1. 程式人生 > >Mybatis源碼解析 - mapper代理對象的生成,你有想過嗎

Mybatis源碼解析 - mapper代理對象的生成,你有想過嗎

行數 生成 ctf 完整 for exe spro exception 關聯

前言

  開心一刻

    本人幼教老師,冬天戴帽子進教室,被小朋友看到,這時候,有個小家夥對我說:老師你的帽子太醜,趕緊摘了吧。我逗他:那你好好學習,以後給老師買個漂亮的?這孩子想都沒想立刻回答:等我賺錢了,帶你去韓國整形

技術分享圖片

簡單示例

  我們先來看一個純粹的mybatis示例(不集成spring等其他框架),代碼很簡單,結構如下

技術分享圖片

  完整代碼地址:mybatis;mapper層和我們平時說的dao層指的是同一個內容,都是數據庫操作的封裝,但是在沒有集成mybatis時,dao層的接口都是需要我們手動去寫其實現類,可在上圖中我們卻發現:我們並沒有手動去實現PersonMapper接口,但工程卻能實實在在的查詢數據庫,獲取我們需要的數據,如下圖所示

技術分享圖片

  從上圖我們發現,PersonMapper實例是一個代理對象,我們操作的其實是PersonMapper的代理實現;也就是說不用我們手動去實現PersonMapper接口,mybatis會動態生成PersonMapper的代理實例,然後由代理實例完成數據庫的操作

  那麽問題來了,mybatis是何時、何地、如何生成mapper代理實例的呢?我們接著往下看

源碼分析

  針對上述問題,我們來跟下mybatis源碼

  SqlSessionFactory的創建

技術分享圖片

    XMLConfigBuilder解析Mybatis配置文件(mybatis-config.xml),將配置文件中各個屬性解析到Configuration實例中,然後以Configuration實例構建SqlSessionFactory(實際是DefaultSqlSessionFactory);其中parseConfiguration方法是解析的具體過程,有興趣的可以更深一步的去探究

技術分享圖片
/**
 * root是以configuration標簽開始的文檔樹
 * 解析配置文件中的各個標簽,並存放到Configuration實例對應的屬性中
 * 解析完成之後,配置文件中的內容全部解析到了Configuration實例中
 * @param root
 */
private void parseConfiguration(XNode root) {
    try {
        //issue #117 read properties first
        propertiesElement(root.evalNode("properties"));                                //
解析配置文件中的properties標簽 Properties settings = settingsAsProperties(root.evalNode("settings")); // 解析配置文件中的settings標簽 loadCustomVfs(settings); typeAliasesElement(root.evalNode("typeAliases")); // 解析配置文件中的typeAliases標簽 pluginElement(root.evalNode("plugins")); // 解析配置文件中的plugins標簽 objectFactoryElement(root.evalNode("objectFactory")); // 解析配置文件中的objectFactory標簽 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); // 解析配置文件中的objectWrapperFactory標簽 reflectorFactoryElement(root.evalNode("reflectorFactory")); // 解析配置文件中的reflectorFactory標簽 settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 environmentsElement(root.evalNode("environments")); // 解析配置文件中的environments標簽 databaseIdProviderElement(root.evalNode("databaseIdProvider")); // 解析配置文件中的databaseIdProvider標簽 typeHandlerElement(root.evalNode("typeHandlers")); // 解析配置文件中的typeHandlers標簽 mapperElement(root.evalNode("mappers")); // 解析配置文件中的mappers標簽 } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
View Code

    上述代碼中的mapperElement(root.evalNode("mappers"));是不是很誘人?與我們的mapper有關系,是不是在這裏就生成了mapper的代理實例,還是只是讀取了mapper配置文件的內容?暫時還不敢肯定,那麽我們跟進去看看

技術分享圖片

    其中有兩個方法值得重點關註下,具體如下,裏面的註釋可以重點看下,有興趣的可以更進一步的跟進去

技術分享圖片
public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));        // 解析映射文件Person.xml
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();    // 將mapper與namespace綁定起來; 將PersonMapper接口與MapperProxyFactory關聯起來
    }

    parsePendingResultMaps();    // 解析Configuration的incompleteResultMaps到Configuration的resultMaps
    parsePendingCacheRefs();    // 解析Configuration的incompleteCacheRefs到Configuration的cacheRefMap
    parsePendingStatements();    // 解析Configuration的incompleteStatements到Configuration的mappedStatements
}

/**
 * context是映射文件:Person.xml的文檔樹,以mapper標簽開始
 * 解析映射文件中的各個標簽,並存放到MapperBuilderAssistant實例對應的屬性中
 */
private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");            // 解析mapper標簽的namespace屬性
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper‘s namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);    // namespace屬性值解析到Configuration的mapperRegistry中
      cacheRefElement(context.evalNode("cache-ref"));    // 解析cache-ref標簽到Configuration的cacheRefMap中
      cacheElement(context.evalNode("cache"));            // 解析cache標簽到Configuration的caches中
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));    // 解析parameterMap標簽到Configuration的parameterMaps中
      resultMapElements(context.evalNodes("/mapper/resultMap"));        // 解析resultMap標簽到Configuration的resultMaps中
      sqlElement(context.evalNodes("/mapper/sql"));                        // 解析sql標簽到XMLMapperBuilder的sqlFragments中
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));    // 解析select|insert|update|delete標簽到Configuration的mappedStatements中
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is ‘" + resource + "‘. Cause: " + e, e);
    }
}
View Code

    此時SqlSessionFactory已經創建,但PersonMapper的代理實例還沒有創建;期間準備了很多東西,包括讀取配置文件和映射文件的內容,並將其放置到Configuration實例的對應屬性中

  SqlSession的創建

技術分享圖片

    實例化了Transaction(JdbcTransaction)、Executor(SimpleExecutor)和SqlSession(DefaultSqlSession),此時mapper代理實例仍未被創建

  Mapper代理對象的創建

技術分享圖片

    可以看到,最終還是利用了JDK的動態代理

protected T newInstance(MapperProxy<T> mapperProxy) {
    // 利用JDK的動態代理生成mapper的代理實例
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

    生成了mapper的代理實例,後續就可以利用此代理實例進行數據庫的操作了

總結

  1、我們用mytabis操作數據庫,有一個固定流程:先創建SqlSessionFactory,然後創建SqlSession,然後再創建獲取mapper代理對象,最後利用mapper代理對象完成數據庫的操作;一次數據庫操作完成後需要關閉SqlSession;

  2、創建SqlSessionFactory實例的過程中,解析mybatis配置文件和映射文件,將內容都存放到Configuration實例的對應屬性中;創建SqlSession的過程中,有創建事務Transaction、執行器Executor,以及DefaultSqlSession;Mapper代理對象的創建,利用的是JDK的動態代理,InvocationHandler是MapperProxy,後續Mapper代理對象方法的執行都會先經過MapperProxy的invoke方法;

  3、很多細節沒有講到,但大體流程就是這樣;另外提下,實際應用中,mybatis往往不會單獨使用,絕大多數都是集成在spring中;關於在spring的集成下,mapper代理對象的創建過程請期待我的下篇博文

Mybatis源碼解析 - mapper代理對象的生成,你有想過嗎