MyBatis初始化

在 MyBatis 初始化过程中,会加载 mybatis-config.xml 配置文件、 Mapper.xml 映射配置文件以及
Mapper 接口中的注解信息,解析后的配置信息会形成相应的对象并保存到 Configuration 对象中。
初始化过程可以分成三部分:

  • 解析 mybatis-config.xml 配置文件
    • SqlSessionFactoryBuilder
    • XMLConfigBuilder
    • Configuration
  • 解析 Mapper.xml 映射配置文件
    • XMLMapperBuilder::parse()
    • XMLStatementBuilder::parseStatementNode ()
    • XMLLanguageDriver
    • SqlSource
    • MappedStatement
  • 解析Mapper接口中的注解
    • MapperRegistry
    • MapperAnnotationBuilder::parse()

解析mybatis-config.xml 配置文件

MyBatis 的初始化流程的入口是 SqlSessionFactoryBuilder::build(Reader reader, String environment, Properties properties) 方法,看看具体流程图:
image.png

  1. public SqlSessionFactory build(InputStream inputStream, String environment,
  2. Properties properties) {
  3. try {
  4. XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment,
  5. properties);
  6. return build(parser.parse());
  7. } catch (Exception e) {
  8. throw ExceptionFactory.wrapException("Error building SqlSession.", e);
  9. } finally {
  10. ErrorContext.instance().reset();
  11. try {
  12. inputStream.close();
  13. } catch (IOException e) {
  14. // Intentionally ignore. Prefer previous error.
  15. }
  16. }
  17. }

首先会使用 XMLConfigBuilder::parser() 解析 mybatis-config.xml 配置文件,

  • 先解析标签 configuration 内的数据封装成 XNode , configuration 也是 MyBatis 中最重要的一个标签
  • 根据 XNode 解析 mybatis-config.xml 配置文件的各个标签转变为各个对象
  1. private void parseConfiguration(XNode root) {
  2. try {
  3. //issue #117 read properties first
  4. propertiesElement(root.evalNode("properties"));
  5. Properties settings = settingsAsProperties(root.evalNode("settings"));
  6. loadCustomVfs(settings);
  7. typeAliasesElement(root.evalNode("typeAliases"));
  8. pluginElement(root.evalNode("plugins"));
  9. objectFactoryElement(root.evalNode("objectFactory"));
  10. objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
  11. reflectorFactoryElement(root.evalNode("reflectorFactory"));
  12. settingsElement(settings);
  13. // read it after objectFactory and objectWrapperFactory issue #631
  14. environmentsElement(root.evalNode("environments"));
  15. databaseIdProviderElement(root.evalNode("databaseIdProvider"));
  16. typeHandlerElement(root.evalNode("typeHandlers"));
  17. mapperElement(root.evalNode("mappers"));
  18. } catch (Exception e) {
  19. throw new BuilderException("Error parsing SQL Mapper Configuration. Cause:
  20. " + e, e);
  21. }
  22. }

再基于 Configuration 使用 SqlSessionFactoryBuilder::build() 生成DefaultSqlSessionFactory 供给后续执行使用。

解析Mapper.xml映射配置文件

首先使用 XMLMapperBuilder::parse() 解析 Mapper.xml ,看看加载流程图来分析分析
image.png
通过 XPathParser::evalNode 将 mapper 标签中内容解析到 XNode

  1. public void parse() {
  2. if (!this.configuration.isResourceLoaded(this.resource)) {
  3. this.configurationElement(this.parser.evalNode("/mapper"));
  4. this.configuration.addLoadedResource(this.resource);
  5. this.bindMapperForNamespace();
  6. }
  7. this.parsePendingResultMaps();
  8. this.parsePendingCacheRefs();
  9. this.parsePendingStatements();
  10. }

再由 configurationElement() 方法去解析 XNode 中的各个标签:

  • namespace
  • parameterMap
  • resultMap
  • select|insert|update|delete
  1. private void configurationElement(XNode context) {
  2. try {
  3. String namespace = context.getStringAttribute("namespace");
  4. if (namespace == null || namespace.equals("")) {
  5. throw new BuilderException("Mapper's namespace cannot be empty");
  6. }
  7. builderAssistant.setCurrentNamespace(namespace);
  8. cacheRefElement(context.evalNode("cache-ref"));
  9. cacheElement(context.evalNode("cache"));
  10. parameterMapElement(context.evalNodes("/mapper/parameterMap"));
  11. resultMapElements(context.evalNodes("/mapper/resultMap"));
  12. sqlElement(context.evalNodes("/mapper/sql"));
  13. //解析MapperState
  14. buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  15. } catch (Exception e) {
  16. throw new BuilderException("Error parsing Mapper XML. The XML location is
  17. '" + resource + "'. Cause: " + e, e);
  18. }
  19. }

其中,基于 XMLMapperBuilder::buildStatementFromContext() ,遍历 、 、 、 节点们,逐个创
建 XMLStatementBuilder 对象,执行解析,通过 XMLStatementBuilder::parseStatementNode() 解
析,

  • parameterType
  • resultType
  • selectKey 等

并会通过 LanguageDriver::createSqlSource() (默认 XmlLanguageDriver )解析动态sql生成
SqlSource (详细内容请看下个小节),

  • 使用 GenericTokenParser::parser() 负责将 SQL 语句中的 #{} 替换成相应的 ? 占位符,并获取该 ? 占位符对应的

而且通过 MapperBuilderAssistant::addMappedStatement() 生成 MappedStatement

  1. public void parseStatementNode() {
  2. //获得 id 属性,编号
  3. String id = context.getStringAttribute("id");
  4. String databaseId = context.getStringAttribute("databaseId");
  5. // 判断 databaseId 是否匹配
  6. if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
  7. return;
  8. }
  9. //解析获得各种属性
  10. Integer fetchSize = context.getIntAttribute("fetchSize");
  11. Integer timeout = context.getIntAttribute("timeout");
  12. String parameterMap = context.getStringAttribute("parameterMap");
  13. String parameterType = context.getStringAttribute("parameterType");
  14. Class<?> parameterTypeClass = resolveClass(parameterType);
  15. String resultMap = context.getStringAttribute("resultMap");
  16. String resultType = context.getStringAttribute("resultType");
  17. String lang = context.getStringAttribute("lang");
  18. //获得 lang 对应的 LanguageDriver 对象
  19. LanguageDriver langDriver = getLanguageDriver(lang);
  20. //获得 resultType 对应的类
  21. Class<?> resultTypeClass = resolveClass(resultType);
  22. String resultSetType = context.getStringAttribute("resultSetType");
  23. //获得 statementType 对应的枚举值
  24. StatementType statementType =
  25. StatementType.valueOf(context.getStringAttribute("statementType",
  26. StatementType.PREPARED.toString()));
  27. //获得 resultSet 对应的枚举值
  28. ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
  29. String nodeName = context.getNode().getNodeName();
  30. //获得 SQL 对应的 SqlCommandType 枚举值
  31. SqlCommandType sqlCommandType =
  32. SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
  33. boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  34. //解析获得各种属性
  35. boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
  36. boolean useCache = context.getBooleanAttribute("useCache", isSelect);
  37. boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
  38. //创建 XMLIncludeTransformer 对象,并替换 <include /> 标签相关的内容
  39. XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration,
  40. builderAssistant);
  41. includeParser.applyIncludes(context.getNode());
  42. //解析 <selectKey /> 标签
  43. processSelectKeyNodes(id, parameterTypeClass, langDriver);
  44. //创建 SqlSource生成动态sql
  45. SqlSource sqlSource = langDriver.createSqlSource(configuration, context,
  46. parameterTypeClass);
  47. String resultSets = context.getStringAttribute("resultSets");
  48. String keyProperty = context.getStringAttribute("keyProperty");
  49. String keyColumn = context.getStringAttribute("keyColumn");
  50. KeyGenerator keyGenerator;
  51. String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
  52. keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
  53. if (configuration.hasKeyGenerator(keyStatementId)) {
  54. keyGenerator = configuration.getKeyGenerator(keyStatementId);
  55. } else {
  56. keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
  57. configuration.isUseGeneratedKeys() &&
  58. SqlCommandType.INSERT.equals(sqlCommandType))
  59. ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
  60. }
  61. //创建 MappedStatement 对象
  62. this.builderAssistant.addMappedStatement(id, sqlSource, statementType,
  63. sqlCommandType, fetchSize, timeout, parameterMap,
  64. parameterTypeClass,
  65. resultMap, resultTypeClass, resultSetTypeEnum, flushCache,
  66. useCache, resultOrdered, (KeyGenerator)keyGenerator,
  67. keyProperty,
  68. keyColumn, databaseId, langDriver, resultSets);
  69. }

解析Mapper接口中的注解

当执行完 XMLMapperBuilder::configurationElement() 方法后,会调用XMLMapperBuilder::bindMapperForNamespace() 会转换成对接口上注解进行扫描,具体通过MapperRegistry::addMapper() 调用 MapperAnnotationBuilder 实现的

image.png

MapperAnnotationBuilder::parse() 是注解构造器,负责解析 Mapper 接口上的注解,解析时需要注意避免和 XMLMapperBuilder::parse() 方法冲突,重复解析,最终使用 parseStatement 解析,那怎么操作?

  1. public void parse() {
  2. String resource = type.toString();
  3. //判断当前 Mapper 接口是否应加载过。
  4. if (!configuration.isResourceLoaded(resource)) {
  5. //加载对应的 XML Mapper,注意避免和 `XMLMapperBuilder::parse()` 方法冲突
  6. loadXmlResource();
  7. //标记该 Mapper 接口已经加载过
  8. configuration.addLoadedResource(resource);
  9. assistant.setCurrentNamespace(type.getName());
  10. //解析 @CacheNamespace 注解
  11. parseCache();
  12. parseCacheRef();
  13. //遍历每个方法,解析其上的注解
  14. Method[] methods = type.getMethods();
  15. for (Method method : methods) {
  16. try {
  17. if (!method.isBridge()) {
  18. //执行解析
  19. parseStatement(method);
  20. }
  21. } catch (IncompleteElementException e) {
  22. configuration.addIncompleteMethod(new MethodResolver(this, method));
  23. }
  24. }
  25. }
  26. //解析待定的方法
  27. parsePendingMethods();
  28. }

那其中最重要的 parseStatement() 是怎么操作?其实跟解析 Mapper.xml 类型主要处理流程类似:

  • 通过加载LanguageDriver , GenericTokenParser 等为生成 SqlSource 动态sql作准备
  • 使用MapperBuilderAssistant::addMappedStatement() 生成注解@mapper , @CacheNamespace 等的 MappedStatement 信息
  1. void parseStatement(Method method) {
  2. //获取接口参数类型
  3. Class<?> parameterTypeClass = getParameterType(method);
  4. //加载语言处理器,默认XmlLanguageDriver
  5. LanguageDriver languageDriver = getLanguageDriver(method);
  6. //根据LanguageDriver,GenericTokenParser生成动态SQL
  7. SqlSource sqlSource = getSqlSourceFromAnnotations(method,
  8. parameterTypeClass, languageDriver);
  9. if (sqlSource != null) {
  10. //获取其他属性
  11. Options options = method.getAnnotation(Options.class);
  12. final String mappedStatementId = type.getName() + "." +
  13. method.getName();
  14. Integer fetchSize = null;
  15. Integer timeout = null;
  16. StatementType statementType = StatementType.PREPARED;
  17. ResultSetType resultSetType = null;
  18. SqlCommandType sqlCommandType = getSqlCommandType(method);
  19. boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  20. boolean flushCache = !isSelect;
  21. boolean useCache = isSelect;
  22. //获得 KeyGenerator 对象
  23. KeyGenerator keyGenerator;
  24. String keyProperty = null;
  25. String keyColumn = null;
  26. if (SqlCommandType.INSERT.equals(sqlCommandType) ||
  27. SqlCommandType.UPDATE.equals(sqlCommandType)) { // 有
  28. // first check for SelectKey annotation - that overrides everything
  29. else
  30. //如果有 @SelectKey 注解,则进行处理
  31. SelectKey selectKey = method.getAnnotation(SelectKey.class);
  32. if (selectKey != null) {
  33. keyGenerator = handleSelectKeyAnnotation(selectKey,
  34. mappedStatementId, getParameterType(method), languageDriver);
  35. keyProperty = selectKey.keyProperty();
  36. //如果无 @Options 注解,则根据全局配置处理
  37. } else if (options == null) {
  38. keyGenerator = configuration.isUseGeneratedKeys() ?
  39. Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
  40. // 如果有 @Options 注解,则使用该注解的配置处理
  41. } else {
  42. keyGenerator = options.useGeneratedKeys() ?
  43. Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
  44. keyProperty = options.keyProperty();
  45. keyColumn = options.keyColumn();
  46. }
  47. // 无
  48. } else {
  49. keyGenerator = NoKeyGenerator.INSTANCE;
  50. }
  51. //初始化各种属性
  52. if (options != null) {
  53. if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
  54. flushCache = true;
  55. } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
  56. flushCache = false;
  57. }
  58. useCache = options.useCache();
  59. fetchSize = options.fetchSize() > -1 || options.fetchSize() ==
  60. Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
  61. timeout = options.timeout() > -1 ? options.timeout() : null;
  62. statementType = options.statementType();
  63. resultSetType = options.resultSetType();
  64. }
  65. // 获得 resultMapId 编号字符串
  66. String resultMapId = null;
  67. //如果有 @ResultMap 注解,使用该注解为 resultMapId 属性
  68. ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
  69. if (resultMapAnnotation != null) {
  70. String[] resultMaps = resultMapAnnotation.value();
  71. StringBuilder sb = new StringBuilder();
  72. for (String resultMap : resultMaps) {
  73. if (sb.length() > 0) {
  74. sb.append(",");
  75. }
  76. sb.append(resultMap);
  77. }
  78. resultMapId = sb.toString();
  79. // 如果无 @ResultMap 注解,解析其它注解,作为 resultMapId 属性
  80. } else if (isSelect) {
  81. resultMapId = parseResultMap(method);
  82. }
  83. //构建 MappedStatement 对象
  84. assistant.addMappedStatement(
  85. mappedStatementId,
  86. sqlSource,
  87. statementType,
  88. sqlCommandType,
  89. fetchSize,
  90. timeout,
  91. // ParameterMapID
  92. null,
  93. parameterTypeClass,
  94. resultMapId,
  95. getReturnType(method),
  96. resultSetType,
  97. flushCache,
  98. useCache,
  99. // TODO gcode issue #577
  100. false,
  101. keyGenerator,
  102. keyProperty,
  103. keyColumn,
  104. // DatabaseID
  105. null,
  106. languageDriver,
  107. // ResultSets
  108. options != null ? nullOrEmpty(options.resultSets()) : null);
  109. }
  110. }

生成动态SqlSource

当在执行 langDriver::createSqlSource(configuration, context, parameterTypeClass) 中的时候, 是怎样从 Mapper XML 或方法注解上读取 SQL 内容生成动态 SqlSource 的呢?现在来一探究竟,
image.png
首先需要获取 langDriver 实现 XMLLanguageDriver / RawLanguageDriver ,现在使用默认的XMLLanguageDriver::createSqlSource(configuration, context, parameterTypeClass) 开启创建,再使用 XMLScriptBuilder::parseScriptNode() 解析生成 SqlSource

  • DynamicSqlSource : 动态的 SqlSource 实现类 , 适用于使用了 OGNL 表达式,或者使用了${} 表达式的 SQL
  • RawSqlSource : 原始的 SqlSource 实现类 , 适用于仅使用 #{} 表达式,或者不使用任何表达式的情况
  1. public SqlSource parseScriptNode() {
  2. MixedSqlNode rootSqlNode = this.parseDynamicTags(this.context);
  3. Object sqlSource;
  4. if (this.isDynamic) {
  5. sqlSource = new DynamicSqlSource(this.configuration, rootSqlNode);
  6. } else {
  7. sqlSource = new RawSqlSource(this.configuration, rootSqlNode,
  8. this.parameterType);
  9. }
  10. return (SqlSource)sqlSource;
  11. }

那就选择其中一种来分析一下 RawSqlSource ,怎么完成构造的呢?看看 RawSqlSource 构造函数:

  1. public RawSqlSource(Configuration configuration, String sql, Class<?>
  2. parameterType) {
  3. SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
  4. Class<?> clazz = parameterType == null ? Object.class : parameterType;
  5. this.sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap());
  6. }

使用 SqlSourceBuilder::parse() 去解析SQl,里面又什么神奇的地方呢?

  1. public SqlSource parse(String originalSql, Class<?> parameterType, Map<String,
  2. Object> additionalParameters) {
  3. SqlSourceBuilder.ParameterMappingTokenHandler handler = new
  4. SqlSourceBuilder.ParameterMappingTokenHandler(this.configuration, parameterType,
  5. additionalParameters);
  6. //创建基于#{}的GenericTokenParser
  7. GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
  8. String sql = parser.parse(originalSql);
  9. return new StaticSqlSource(this.configuration, sql,
  10. handler.getParameterMappings());
  11. }

ParameterMappingTokenHandler 是 SqlSourceBuilder 的内部私有静态类,ParameterMappingTokenHandler ,负责将匹配到的 #{ 和 } 对,替换成相应的 ? 占位符,并获取
该 ? 占位符对应的 org.apache.ibatis.mapping.ParameterMapping 对象。

并基于 ParameterMappingTokenHandler 使用 GenericTokenParser::parse() 将SQL中的 #{} 转化占位符 ? 占位符后创建一个 StaticSqlSource 返回。

总结

在 MyBatis 初始化过程中,会加载 mybatis-config.xml 配置文件、 Mapper.xml 映射配置文件以及Mapper 接口中的注解信息,解析后的配置信息会形成相应的对象并全部保存到 Configuration 对象中,并创建 DefaultSqlSessionFactory 供SQl执行过程创建出顶层接口 SqlSession 供给用户进行操作。