天天看點

MyBatis整體預覽(一)

題記:最近在工作之餘分析了一下MyBatis的源碼,促使我閱讀源碼的原因是為了實作MyBatis在實體上的分頁。我們知道,MyBatis是在邏輯上的分頁,通過使用者的查詢,将結果緩存下來,在檢視是否傳遞了RowBounds對象,在檢視裡面的offset和limit值,通過這兩個值,從傳回的結果集合中截取位于期間的值。但是這樣并不是很好,可以想想,如果假設查詢的資料量很大,但是有用的可以是前幾條,這未免有點太浪費了。在之前,也在網上查了一下實作分頁的方法,最常用的就是添加MyBatis插件,實作Interceptor接口,攔截StatementHandler接口中的prepare方法,後面會介紹為什麼攔截這個接口的這個方法。在攔截ResultSetHandler接口的handlerResultSet方法,後面也會對其緣由進行介紹。但是這中方法雖然可以添加分頁的SQL語句,但是并沒有将分頁的offset和limit的值讓Mybatis動态的添加到SQL中去,有人會說,可以在攔截StatementHandler接口的時候我們将它們拼裝上去。但是這樣會容易出現SQL注入的問題。是以這樣不得不使我進一步的了解MyBatis的内部原理。本文将就一下幾個方面對MyBatis的内部實作進行分析。

資料管家——Configuration:

    MyBatis在運作期的基本上所有的資料都會彙總到這個類。它的初始資料是來自開發人員配置在configuration的xml配置檔案。通過使用者配置的environments來獲得系統運作的資料庫環境,如事物管理以及資料源。下面給出了最基本的配置:

[html]

<configuration> 

<environments default="development"> 

<environment id="development"> 

<transactionManager type="JDBC" /> 

<dataSource type="POOLED"> 

<property name="driver" value="com.mysql.jdbc.Driver"/> 

<property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=GBK"/> 

<property name="username" value="root"/> 

<property name="password" value="root"/> 

</dataSource> 

</environment> 

</environments> 

<mappers> 

<mapper resource="com/bieber/mybatis/io/user-mapper.xml"/> 

</mappers> 

</configuration> 

這些配置對于MyBatis需要做哪些工作呢?通過閱讀Configuration的源碼會發現,Mybatis其實為configuration标簽下面的子标簽都有一個對應的變量來進行存儲,例如:

[java]

protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(); 

則是存儲<typeHandlers></typeHandlers>标簽下面配置的所有資訊。其他的也類似可以找到。負責建立Configuration對象的則是XMLConfigurationBuilder,這裡将完成從配置的XML資料映射到Configuration對象的資料。通過一下方法完成資料的映射:

[java]

private void parseConfiguration(XNode root) { 

    try { 

    propertiesElement(root.evalNode("properties"));         typeAliasesElement(root.evalNode("typeAliases")); 

        pluginElement(root.evalNode("plugins")); 

        objectFactoryElement(root.evalNode("objectFactory")); 

                  objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); 

      settingsElement(root.evalNode("settings")); 

      environmentsElement(root.evalNode("environments")); 

      databaseIdProviderElement(root.evalNode("databaseIdProvider")); 

      typeHandlerElement(root.evalNode("typeHandlers")); 

      mapperElement(root.evalNode("mappers")); 

    } catch (Exception e) { 

      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); 

    } 

  } 

可以到,它為每個元素都對應了一個處理方法,這些方法将負責解析我們配置的XML檔案。這裡面我主要跟蹤了幾個方法的執行 www.2cto.com

(mapperElement,typeHandlerElement,typeAliasesElement,environmentsElement)

mapperElement——ORM

       我們知道,MyBatis支援注解形式和XML形式的ORM配置。那麼當然将會有兩個類來處理這兩種行為,它們分别是XMLMapperBuilder和MapperAnnotationBuilder,它們分别處理什麼類型,我看我就不用說了。通過解析configuration/mappers元素來獲得ORM配置資訊。

1)XML方式的ORM配置和方式,當我們在mappers/mapper的屬性中配置了url或者是resource資訊的時候将觸發MyBatis采用XML的方式進行處理,并讀取你指定的mapper路徑。在XMLMapperBuilder類中有如下方法:

[java]

private void configurationElement(XNode context) { 

    try { 

      String namespace = context.getStringAttribute("namespace"); 

      builderAssistant.setCurrentNamespace(namespace); 

      cacheRefElement(context.evalNode("cache-ref")); 

      cacheElement(context.evalNode("cache")); 

      parameterMapElement(context.evalNodes("/mapper/parameterMap")); 

      resultMapElements(context.evalNodes("/mapper/resultMap")); 

      sqlElement(context.evalNodes("/mapper/sql")); 

      buildStatementFromContext(context.evalNodes("select|insert|update|delete")); 

    } catch (Exception e) { 

      throw new RuntimeException("Error parsing Mapper XML. Cause: " + e, e); 

    } 

  } 

這個方法便是讀取你mapper檔案中所有制的ORM資訊。該方法将通過調用XMLMapperBuilder的parse()方法觸發。

    2)注解方式配置ORM資訊加載,當你配置了mappers/package或者在mapper裡面配置了class屬性的時候将觸發資訊的讀取,具體的過程我就再描述了,基本和上面差不多,隻是讀取的是注解的資訊。

    注意:MyBatis優先處理的是注解形式的方式,并且在mapper配置中,當配置了多個屬性時,resource屬性優先處理。

    那麼在這樣處理後Configuration會得到怎樣的資料呢?通過這些處理在Configuration裡面将會獲得幾個主要的變量值:sqlFragments,resultMaps,mappedStatements。其中sqlFragments就是我們定義在mapper裡面的sql标簽或者注解的内容,而resultMaps也是定義在mapper裡面或者注解的resultMap内容。最重要的是mappedStatements,這是ORM的最關鍵部分。它裡面通過鍵值對的方式存儲,key這是我們配置的id屬性加上namespace,而value則是MappedStatement對象,這個對象這就對應了我們配置的select/update/delete/insert标簽的值。

    MappedStatement對象包含這條slq語句的ID,執行的類型(Inser,update,delte,select),statementType(指定産生Statement的類型,如PreparedStatement),還有一個就是SqlSource接口的子類對象,在MyBatis中有兩種SqlSource,一種是動态的,另一種是靜态的。不用解釋,應該都明白,一個是生成動态SQL用的,另一個這是簡單靜态的SQL。在SqlSource中,包括你定義的SQL語句,以及引入的外部SQL語句塊。MappedStatement最後還要包括一個重要的資訊,這就是ParameterMap,這直接關系你定義的SQL語句中通過#{propertyName}定義的動态填充值。如果你的是一個POJO對象,那麼MyBatis将會通過反射獲得這個對象的屬性,并依次填入到對應的propertyName所在的位置。

    注意:此時的MappedStatement中的SQL語句還是帶有#{propertyName}這樣占位符的字元串,還并沒有解析成待問号(?)的占位符。要執行該操作是在執行具體的資料庫操作的時候才替換成(?),隻是為了很好的找到這個propertyName所對應的值所在的位置。

以上就将整個SqlSession的初始化過程所做的操作進行了解剖。完成這些操作之後,那麼就等待使用者觸發對資料庫的操作了。

   後續将會給出,MyBatis是如何觸發使用者自定義的插件的過程以及開發自己的TypeHandler。MyBatis允許使用者的插件可以攔截ParameterHandler,ResultSetHandler,StatementHandler,Executor接口,進而進行一些操作。

   本文到此繼續,後續會有新的更新。如有嚴重不對的地方,還望各位能夠及時提出,畢竟對MyBatis的接觸也隻有一個星期,未免有些地方不對,還望大家諒解。

繼續閱讀