MyBatis源碼學習(三)——創建SqlSessionFactory實例和Mapper代理工廠

先上小結:

  1. 開始調用MybatisAutoConfiguration.sqlSessionFactory()方法。
  2. 構建SqlSessionFactoryBean類實例。
  3. 把數據源實例DataSource賦值給SqlSessionFactoryBean。
  4. 創建Configuration實例,賦值給SqlSessionFactoryBean。
  5. 讀取mybatis配置文件,把mapperLocations參數賦值給SqlSessionFactoryBean。
  6. SqlSessionFactoryBean開始創建SqlSessionFactory實例。
  7. 處理typeAliasesPackage。
  8. configuration添加插件plugins到攔截器鏈interceptorChain中。
  9. 註冊typeHandlersPackage和typeHandlers。
  10. 處理databaseIdProvider,這是數據庫廠商標識。
  11. 初始化緩存cache。
  12. 創建transactionFactory,並由SqlSessionFactoryBean的environment,transactionFactory,dataSource組成configuration的environment。
  13. 開始處理mapperLocations,讀取路徑下的Mapper.xml文件,並開始解析。
  14. 註冊Mapper.xml中<mapper>節點的namespace參數,然後開始處理/mapper/resultMap節點。
  15. 處理resultMap節點的id,type,extends,autoMapping參數。
  16. 處理resultMap節點的子節點,創建ResultMapping實例,組裝property,column,javaType等參數。
  17. 組裝ResultMap實例。resultMap節點處理完成。
  18. 處理/mapper/sql節點,加入configuration實例的sqlFragments這個Map中。
  19. 處理select|insert|update|delete節點。生成MappedStatement實例,添加到configuration的mappedStatements這個Map中。mapper節點處理完畢。
  20. 把xml文件對應的resource添加到已加載列表中,把nameSpace添加到已加載列表中。
  21. 由namespace代表的Mapper接口創建其代理工廠MapperProxyFactory,加入configuration的knownMapper中。
  22. 處理Mapper接口方法上的@Insert等註解,並轉換成mappedStatements,然後添加到configuration的mappedStatements這個Map中,流程和從Mapper.xml中處理動態sql時差不多。
  23. 創建SqlSessionFactory實例,把configuration實例賦值給此實例。

 

正文:

經過一番調用Spring終於開始調用MybatisAutoConfiguration類的sqlSessionFactory()方法,開始構建SqlSessionFactory類實例:

@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    factory.setVfs(SpringBootVFS.class);
    if (StringUtils.hasText(this.properties.getConfigLocation())) {
        factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
    }

    org.apache.ibatis.session.Configuration configuration = this.properties.getConfiguration();
    if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
        configuration = new org.apache.ibatis.session.Configuration();
    }

    if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
        Iterator var4 = this.configurationCustomizers.iterator();

        while(var4.hasNext()) {
            ConfigurationCustomizer customizer = (ConfigurationCustomizer)var4.next();
            customizer.customize(configuration);
        }
    }

    factory.setConfiguration(configuration);
    if (this.properties.getConfigurationProperties() != null) {
        factory.setConfigurationProperties(this.properties.getConfigurationProperties());
    }

    if (!ObjectUtils.isEmpty(this.interceptors)) {
        factory.setPlugins(this.interceptors);
    }

    if (this.databaseIdProvider != null) {
        factory.setDatabaseIdProvider(this.databaseIdProvider);
    }

    if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
        factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
    }

    if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
        factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
    }

    if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
        factory.setMapperLocations(this.properties.resolveMapperLocations());
    }

    return factory.getObject();
}

方法先是創建了一個SqlSessionFactoryBean實例,並把數據源實例DataSource賦值給factory。

然後創建Configuration實例,也賦值給factory。

下面給factory賦值了databaseIdProvider,如果沒配置databaseId則不賦值。databaseId是多數據源的配置。

後面的TypeHandlersPackage相關的代碼是自定義類型轉化時用的。

再後面的一段代碼值得關注一下,也就是這段:

if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
    factory.setMapperLocations(this.properties.resolveMapperLocations());
}

這裏的this.properties就是MybatisAutoConfiguration類的MybatisProperties參數,是這麼定義的:

private final MybatisProperties properties;

這段代碼的作用就是把MybatisProperties中的mapperLocations參數賦值給SqlSessionFactory。

MybatisProperties中的mapperLocations參數來自項目的配置文件,比如application.properties文件中配置:

mybatis.mapperLocations=classpath:mybatis/mappers/*.xml

至於這個配置如何加載到MybatisProperties參數中,在以後的文章中單獨解析。

到了方法最後:

return factory.getObject();

就是由SqlSessionFactoryBean創建SqlSessionFactory實例的方法了,方法源碼如下:

public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
        this.afterPropertiesSet();
    }

    return this.sqlSessionFactory;
}

初始化時,SqlSessionFactoryBean中的sqlSessionFactory就是null,所以要執行SqlSessionFactoryBean的afterPropertiesSet()方法,來創建sqlSessionFactory對象,代碼如下:

public void afterPropertiesSet() throws Exception {
    Assert.notNull(this.dataSource, "Property 'dataSource' is required");
    Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    Assert.state(this.configuration == null && this.configLocation == null || this.configuration == null || this.configLocation == null, "Property 'configuration' and 'configLocation' can not specified with together");
    this.sqlSessionFactory = this.buildSqlSessionFactory();
}

主要就是最後一行,調用this.buildSqlSessionFactory()方法:

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    XMLConfigBuilder xmlConfigBuilder = null;
    Configuration configuration;
    if (this.configuration != null) {
        configuration = this.configuration;
        if (configuration.getVariables() == null) {
            configuration.setVariables(this.configurationProperties);
        } else if (this.configurationProperties != null) {
            configuration.getVariables().putAll(this.configurationProperties);
        }
    } else if (this.configLocation != null) {
        xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);
        configuration = xmlConfigBuilder.getConfiguration();
    } else {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
        }

        configuration = new Configuration();
        if (this.configurationProperties != null) {
            configuration.setVariables(this.configurationProperties);
        }
    }

    if (this.objectFactory != null) {
        configuration.setObjectFactory(this.objectFactory);
    }

    if (this.objectWrapperFactory != null) {
        configuration.setObjectWrapperFactory(this.objectWrapperFactory);
    }

    if (this.vfs != null) {
        configuration.setVfsImpl(this.vfs);
    }

    String[] typeHandlersPackageArray;
    String[] var4;
    int var5;
    int var6;
    String packageToScan;
    if (StringUtils.hasLength(this.typeAliasesPackage)) {
        typeHandlersPackageArray = StringUtils.tokenizeToStringArray(this.typeAliasesPackage, ",; \t\n");
        var4 = typeHandlersPackageArray;
        var5 = typeHandlersPackageArray.length;

        for(var6 = 0; var6 < var5; ++var6) {
            packageToScan = var4[var6];
            configuration.getTypeAliasRegistry().registerAliases(packageToScan, this.typeAliasesSuperType == null ? Object.class : this.typeAliasesSuperType);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
            }
        }
    }

    int var27;
    if (!ObjectUtils.isEmpty(this.typeAliases)) {
        Class[] var25 = this.typeAliases;
        var27 = var25.length;

        for(var5 = 0; var5 < var27; ++var5) {
            Class<?> typeAlias = var25[var5];
            configuration.getTypeAliasRegistry().registerAlias(typeAlias);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Registered type alias: '" + typeAlias + "'");
            }
        }
    }

    if (!ObjectUtils.isEmpty(this.plugins)) {
        Interceptor[] var26 = this.plugins;
        var27 = var26.length;

        for(var5 = 0; var5 < var27; ++var5) {
            Interceptor plugin = var26[var5];
            configuration.addInterceptor(plugin);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Registered plugin: '" + plugin + "'");
            }
        }
    }

    if (StringUtils.hasLength(this.typeHandlersPackage)) {
        typeHandlersPackageArray = StringUtils.tokenizeToStringArray(this.typeHandlersPackage, ",; \t\n");
        var4 = typeHandlersPackageArray;
        var5 = typeHandlersPackageArray.length;

        for(var6 = 0; var6 < var5; ++var6) {
            packageToScan = var4[var6];
            configuration.getTypeHandlerRegistry().register(packageToScan);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");
            }
        }
    }

    if (!ObjectUtils.isEmpty(this.typeHandlers)) {
        TypeHandler[] var28 = this.typeHandlers;
        var27 = var28.length;

        for(var5 = 0; var5 < var27; ++var5) {
            TypeHandler<?> typeHandler = var28[var5];
            configuration.getTypeHandlerRegistry().register(typeHandler);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Registered type handler: '" + typeHandler + "'");
            }
        }
    }

    if (this.databaseIdProvider != null) {
        try {
            configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
        } catch (SQLException var24) {
            throw new NestedIOException("Failed getting a databaseId", var24);
        }
    }

    if (this.cache != null) {
        configuration.addCache(this.cache);
    }

    if (xmlConfigBuilder != null) {
        try {
            xmlConfigBuilder.parse();
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
            }
        } catch (Exception var22) {
            throw new NestedIOException("Failed to parse config resource: " + this.configLocation, var22);
        } finally {
            ErrorContext.instance().reset();
        }
    }

    if (this.transactionFactory == null) {
        this.transactionFactory = new SpringManagedTransactionFactory();
    }

    configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
    if (!ObjectUtils.isEmpty(this.mapperLocations)) {
        Resource[] var29 = this.mapperLocations;
        var27 = var29.length;

        for(var5 = 0; var5 < var27; ++var5) {
            Resource mapperLocation = var29[var5];
            if (mapperLocation != null) {
                try {
                    XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments());
                    xmlMapperBuilder.parse();
                } catch (Exception var20) {
                    throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", var20);
                } finally {
                    ErrorContext.instance().reset();
                }

                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
                }
            }
        }
    } else if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
    }

    return this.sqlSessionFactoryBuilder.build(configuration);
}

這個方法先是判斷了configuration變量是否存在,configuration之前創建過了,此處略過。

 

下一部分代碼處理了typeAliasesPackage,這部分的功能是處理實體類別名。根據配置的路徑,掃描路徑下的POJO類,並把這些類進行註冊。

關於typeAliasesPackage:

這是個可配置的路徑,也可以不配置,作用是給Mapper.xml文件中用到的POJO類起別名,resultType,resultMap,parameterType中的用到的類。

比如可以通過以下方式配置:

mybatis.type-aliases-package=com.macro.mall.mapper

如果不配置,在Mapper.xml文件中寫POJO類的帶路徑全名也行。

後面有關this.plugins的代碼是處理mybatis的插件,通過:

configuration.addInterceptor(plugin);

把插件加到configuration的interceptorChain變量中,interceptorChain是一個鏈狀的結構。

 

後面關於this.typeHandlersPackage和this.typeHandlers的代碼,處理了mybatis的類型處理器,typeHandlersPackage是通過路徑的方式配置類型處理器,typeHandlers是直接指定類型處理器類。

 

再後面關於this.databaseIdProvider的代碼,處理的是數據庫廠商標識,在配置文件中配置了databaseIdProvider後,在Mapper.xml文件中給每個sql配置databaseId,可以指定使用的數據庫類別。

 

後面關於this.cache的代碼是處理mybatis緩存用的。配置的cache被加入configuration的caches參數,caches是Map<String, Cache>類別。

 

後面關於xmlConfigBuilder的代碼,是用xmlConfigBuilder解析xml文件。

但是根據之前的代碼,只要先前創建過了configuration,這個xmlConfigBuilder就會是null,也就是方法最開始的這一段:

XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
    configuration = this.configuration;
    if (configuration.getVariables() == null) {
        configuration.setVariables(this.configurationProperties);
    } else if (this.configurationProperties != null) {
        configuration.getVariables().putAll(this.configurationProperties);
    }
} else if (this.configLocation != null) {
    xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);
    configuration = xmlConfigBuilder.getConfiguration();
}

configuration不是null的時候,就不會設置xmlConfigBuilder了。

 

後面的代碼,關於transactionFactory,是mybatis的事務工廠,默認由SpringManagedTransactionFactory類實現。

 

下一步,由SqlSessionFactoryBean的environment,transactionFactory,dataSource組成configuration的environment:

configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));

 

下一步,關於this.mapperLocations的處理,是對掃描到的Mapper.xml文件的加載。

這裏的this.mapperLocations是一個Resource數組,指的就是之前掃描到的所有Mapper.xml文件對應的Resource。

對於每一個Resource,創建一個新的XMLMapperBuilder對象,然後調用其parse()方法:

public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
    configurationElement(parser.evalNode("/mapper"));
    configuration.addLoadedResource(resource);
    bindMapperForNamespace();
  }

  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}

對於configuration.isResourceLoaded()方法,configuration維護了一個loadedResources變量,是記錄了加載好的Mapper.xml文件名的Set<String>變量。

對於沒有被加載的Mapper.xml文件,首先調用configurationElement()方法,參數是xml文件中的<mapper>標籤:

private void configurationElement(XNode context) {
  try {
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.equals("")) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    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 BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  }
}

可見,方法對Mapper.xml配置文件中的namespace存到builderAssistant中,然後分別處理了xml文件中的cache-ref節點,cache節點,/mapper/parameterMap節點,/mapper/resultMap節點,/mapper/sql節點,其中/mapper/resultMap節點的處理方法resultMapElements(),經過一番簡單的調用和循環,最後調用如下方法:

private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
  ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
  String id = resultMapNode.getStringAttribute("id",
      resultMapNode.getValueBasedIdentifier());
  String type = resultMapNode.getStringAttribute("type",
      resultMapNode.getStringAttribute("ofType",
          resultMapNode.getStringAttribute("resultType",
              resultMapNode.getStringAttribute("javaType"))));
  String extend = resultMapNode.getStringAttribute("extends");
  Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
  Class<?> typeClass = resolveClass(type);
  Discriminator discriminator = null;
  List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
  resultMappings.addAll(additionalResultMappings);
  List<XNode> resultChildren = resultMapNode.getChildren();
  for (XNode resultChild : resultChildren) {
    if ("constructor".equals(resultChild.getName())) {
      processConstructorElement(resultChild, typeClass, resultMappings);
    } else if ("discriminator".equals(resultChild.getName())) {
      discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
    } else {
      List<ResultFlag> flags = new ArrayList<ResultFlag>();
      if ("id".equals(resultChild.getName())) {
        flags.add(ResultFlag.ID);
      }
      resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
    }
  }
  ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
  try {
    return resultMapResolver.resolve();
  } catch (IncompleteElementException  e) {
    configuration.addIncompleteResultMap(resultMapResolver);
    throw e;
  }
}

首先獲取了resultMap節點的id,type,extends,autoMapping這四個參數。

extends參數可以用來繼承另外一個resultMap節點。

autoMapping參數如果設爲true,則自動匹配數據庫字段名小寫後的java屬性,設爲false或不設置則需要在resultMap節點中使用result節點手工匹配。

第二步,開始處理resultMap節點的子節點。處理的結果是創建了很多ResultMapping實例,並加入resultMappings列表中。

如果子節點是constructor節點,則調用processConstructorElement()方法處理。constructor節點是當resultMap的type類沒有無參構造時使用的。

如果子節點是discriminator節點,則調用processDiscriminatorElement()節點處理。discriminator節點是鑑別器,利用某個字段的值,區分使用不同的子結果集。

如果子節點是其他節點,基本也就剩下result節點了,調用的是buildResultMappingFromContext()方法。下面來看一下這個方法:

private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
  String property;
  if (flags.contains(ResultFlag.CONSTRUCTOR)) {
    property = context.getStringAttribute("name");
  } else {
    property = context.getStringAttribute("property");
  }
  String column = context.getStringAttribute("column");
  String javaType = context.getStringAttribute("javaType");
  String jdbcType = context.getStringAttribute("jdbcType");
  String nestedSelect = context.getStringAttribute("select");
  String nestedResultMap = context.getStringAttribute("resultMap",
      processNestedResultMappings(context, Collections.<ResultMapping> emptyList()));
  String notNullColumn = context.getStringAttribute("notNullColumn");
  String columnPrefix = context.getStringAttribute("columnPrefix");
  String typeHandler = context.getStringAttribute("typeHandler");
  String resultSet = context.getStringAttribute("resultSet");
  String foreignColumn = context.getStringAttribute("foreignColumn");
  boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
  Class<?> javaTypeClass = resolveClass(javaType);
  @SuppressWarnings("unchecked")
  Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
  JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
  return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}

內容其實很簡單,獲取了result節點的property,column,javaType,jdbcType,select,resultMap,notNullColumn,columnPrefix,typeHandler,resultSet,foreignColumn,fetchType這些屬性,這些屬性不一定都是必填的,每個參數的具體功能這裏就不寫了。然後把這些屬性都加到builderAssistant.buildResultMapping()方法中生成ResultMapping實例並返回。

 

回到對resultMap節點的處理,上面是第二步,下面第三步也就是最後一步,創建resultMapResolver實例,並調用其resolve()方法組裝了ResultMap實例然後返回。

至此resultMap節點處理完畢。

 

回到XMLMapperBuilder的configurationElement()方法,在處理完了resultMap節點後,還要處理/mapper/sql節點:

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

其方法代碼是:

private void sqlElement(List<XNode> list) throws Exception {
  if (configuration.getDatabaseId() != null) {
    sqlElement(list, configuration.getDatabaseId());
  }
  sqlElement(list, null);
}

根據是否存在dataBaseId,決定是否要先調用一次有dataBaseId的sqlElement()方法,注意這裏不是if/else,也就是說,如果存在dataBaseId,sqlElement()方法實際上調用了兩遍。

另外,dataBaseId來自前面關於dataBaseProvider的配置。

下面看看sqlElement()方法的代碼:

private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
  for (XNode context : list) {
    String databaseId = context.getStringAttribute("databaseId");
    String id = context.getStringAttribute("id");
    id = builderAssistant.applyCurrentNamespace(id, false);
    if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
      sqlFragments.put(id, context);
    }
  }
}

其中的databaseIdMatchesCurrent()方法其實是做了一個按id去重的工作,在一個xml文件中的sql節點,如果id相同,只加載第一個。處理好的sql節點加入configuration實例的sqlFragments這個map中。

至此/mapper/sql節點處理完畢。

 

再次回到XMLMapperBuilder的configurationElement()方法,處理完了/mapper/sql節點後,開始處理select|insert|update|delete節點:

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

方法代碼是:

private void buildStatementFromContext(List<XNode> list) {
  if (configuration.getDatabaseId() != null) {
    buildStatementFromContext(list, configuration.getDatabaseId());
  }
  buildStatementFromContext(list, null);
}

和之前/mapper/sql節點的處理思路差不多,也是按照是否有dataBaseId決定是否多調一次接口,其中buildStatementFromContext()方法的代碼是:

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
  for (XNode context : list) {
    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
    try {
      statementParser.parseStatementNode();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteStatement(statementParser);
    }
  }
}

創建了一個XMLStatementBuilder然後調用他的parseStatementNode()方法:

public void parseStatementNode() {
  String id = context.getStringAttribute("id");
  String databaseId = context.getStringAttribute("databaseId");

  if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
    return;
  }

  Integer fetchSize = context.getIntAttribute("fetchSize");
  Integer timeout = context.getIntAttribute("timeout");
  String parameterMap = context.getStringAttribute("parameterMap");
  String parameterType = context.getStringAttribute("parameterType");
  Class<?> parameterTypeClass = resolveClass(parameterType);
  String resultMap = context.getStringAttribute("resultMap");
  String resultType = context.getStringAttribute("resultType");
  String lang = context.getStringAttribute("lang");
  LanguageDriver langDriver = getLanguageDriver(lang);

  Class<?> resultTypeClass = resolveClass(resultType);
  String resultSetType = context.getStringAttribute("resultSetType");
  StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

  String nodeName = context.getNode().getNodeName();
  SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
  boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
  boolean useCache = context.getBooleanAttribute("useCache", isSelect);
  boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

  // Include Fragments before parsing
  XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
  includeParser.applyIncludes(context.getNode());

  // Parse selectKey after includes and remove them.
  processSelectKeyNodes(id, parameterTypeClass, langDriver);
  
  // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
  SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  String resultSets = context.getStringAttribute("resultSets");
  String keyProperty = context.getStringAttribute("keyProperty");
  String keyColumn = context.getStringAttribute("keyColumn");
  KeyGenerator keyGenerator;
  String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
  keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
  if (configuration.hasKeyGenerator(keyStatementId)) {
    keyGenerator = configuration.getKeyGenerator(keyStatementId);
  } else {
    keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
        configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
        ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
  }

  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
      fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
      resultSetTypeEnum, flushCache, useCache, resultOrdered, 
      keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

和之前的套路也差不多,獲取所有能獲取的參數值,另外對於insert節點來說,額外提供了對於selectKey的處理和useGeneratedKeys的處理,如果useGeneratedKeys參數設爲true,則使用Jdbc3KeyGenerator類的實例作爲KeyGenerator,這是一個用static和final標識的實例。

最後還是把這一大堆參數都傳給builderAssistant.addMappedStatement()方法,生成MappedStatement實例,添加到configuration實例的mappedStatements這個map中。

這裏的MappedStatement實例,在將來調用Mapper代理的具體某個方法的時候會用上,用來生成SqlCommand,進而生成MapperMethod,Mapper的動態代理調用的就是MapperMethod的execute()方法。

至此select|insert|update|delete節點處理完畢。

這樣XMLMapperBuilder對/mapper節點就都處理完畢了。

 

回到XMLMapperBuilder的parse()方法:

public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
    configurationElement(parser.evalNode("/mapper"));
    configuration.addLoadedResource(resource);
    bindMapperForNamespace();
  }

  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}

處理完/mapper節點後,調用addLoadedResource()方法,把這個xml文件對應的resource字符串添加到已加載列表中,然後調用bindMapperForNamespace()方法:

private void bindMapperForNamespace() {
  String namespace = builderAssistant.getCurrentNamespace();
  if (namespace != null) {
    Class<?> boundType = null;
    try {
      boundType = Resources.classForName(namespace);
    } catch (ClassNotFoundException e) {
      //ignore, bound type is not required
    }
    if (boundType != null) {
      if (!configuration.hasMapper(boundType)) {
        // Spring may not know the real resource name so we set a flag
        // to prevent loading again this resource from the mapper interface
        // look at MapperAnnotationBuilder#loadXmlResource
        configuration.addLoadedResource("namespace:" + namespace);
        configuration.addMapper(boundType);
      }
    }
  }
}

作用有兩個,

一是這行:

configuration.addLoadedResource("namespace:" + namespace);

把nameSpace字符串也添加到已加載列表中,這樣對於一個xml來說在已加載列表中就有兩項,大概是這樣兩個字符串:

namespace:com.test.dao.OrderMapper

file [D:\workspace\test\module\target\classes\mybatis\mappers\OrderMapper.xml]

二是把namespace代表的類加入configuration的knownMapper中,也就是這一行:

configuration.addMapper(boundType);

addMapper()方法的代碼:

public <T> void addMapper(Class<T> type) {
  mapperRegistry.addMapper(type);
}

MapperRegistry類中有一個knownMappers 屬性,類型是HashMap<Class<?>, MapperProxyFactory<?>>,用於保存已經加載好的動態代理,其中的key就是Mapper接口類,其addMapper()方法如下:

public <T> void addMapper(Class<T> type) {
  if (type.isInterface()) {
    if (hasMapper(type)) {
      throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
    }
    boolean loadCompleted = false;
    try {
      knownMappers.put(type, new MapperProxyFactory<T>(type));
      // It's important that the type is added before the parser is run
      // otherwise the binding may automatically be attempted by the
      // mapper parser. If the type is already known, it won't try.
      MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
      parser.parse();
      loadCompleted = true;
    } finally {
      if (!loadCompleted) {
        knownMappers.remove(type);
      }
    }
  }
}

可見,方法一開始重新做了一次hasMapper()的校驗。

然後調用knownMappers.put()方法添加對應Mapper接口類的動態代理。

動態代理的生成是new MapperProxyFactory<T>(type)

MapperProxyFactory類是Mapper接口類的代理工廠類,他的內容很簡單:

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethod> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

可見,之前的type是MapperProxyFactory的一個參數,用來生成動態代理。

把MapperProxyFactory工廠類添加到knownMappers後,執行下面這兩行:

MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();

這兩行代碼的功能處理Mapper類中,在方法上面寫註解和動態sql的情況,比如這樣的方法:

@Insert({
        "insert into user (user_name)",
        "values (#{userName,jdbcType=VARCHAR})"
})
int insert(User user);

還記得前面有段代碼邏輯,是從Mapper.xml中獲得動態sql,轉換成MappedStatement並添加給configuration。這裏的邏輯也是一樣,只不過變成了從方法的註解中獲取動態sql,對開發者來說,這只是兩種不同的配置動態sql的方式。

 

通過以上流程,bindMapperForNamespace()方法執行完成,我們獲得了Mapper接口類的動態代理工廠類,並把它添加到了configuration的knownMappers中,後面動態代理的生成和使用做好了準備。

 

回到XMLMapperBuilder的parse()方法,bindMapperForNamespace()執行完後,最後了三個方法:

parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();

是把之前創建失敗的ResultMap,CacheRefs,Statement,再次嘗試解析,然後從失敗列表中刪除。

至此XMLMapperBuilder的parse()方法結束。

 

接下來回到SqlSessionFactoryBean的buildSqlSessionFactory()方法,來到最後的:

return this.sqlSessionFactoryBuilder.build(configuration);

經過上面的一頓操作,已經把xml文件裏的元素都解析並且添加到了configuration實例中,最後就是調用build()方法,生成一個SqlSessionFactory實例:

public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config);
}


public DefaultSqlSessionFactory(Configuration configuration) {
  this.configuration = configuration;
}

SqlSessionFactory裏的變量其實就只有這個configuration。

於是,SqlSessionFactory實例創建成功,當我們在代碼中使用mybatis時,就會使用這個實例。

(本文結束)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章