关于面试:面试你知道MyBatis执行过程之初始化是如何执行的吗

44次阅读

共计 12901 个字符,预计需要花费 33 分钟才能阅读完成。

该系列文章收录在公众号【Ccww 技术博客】,原创技术文章早于博客推出

前言

在理解 MyBatis 架构以及核心内容剖析后,咱们能够钻研 MyBatis 执行过程,包含

  • MyBatis 初始化
  • SQL 执行过程

而且在面试会问到一下对于 MyBatis 初始化的问题,比方:

  • Mybatis 须要初始化哪些?
  • MyBatis 初始化的过程?

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) 办法,看看具体流程图:

 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {ErrorContext.instance().reset();
      try {inputStream.close();
      } catch (IOException e) {// Intentionally ignore. Prefer previous error.}
    }
  }

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

  • 先解析标签 configuration 内的数据封装成XNodeconfiguration 也是 MyBatis 中最重要的一个标签
  • 依据 XNode 解析mybatis-config.xml 配置文件的各个标签转变为各个对象
private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      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);
    }
  }

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

解析 Mapper.xml 映射配置文件

首先应用 XMLMapperBuilder::parse() 解析Mapper.xml,看看加载流程图来剖析剖析

通过 XPathParser::evalNodemapper标签中内容解析到XNode

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

    this.parsePendingResultMaps();
    this.parsePendingCacheRefs();
    this.parsePendingStatements();}

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

  • namespace
  • parameterMap
  • resultMap
  • select|insert|update|delete
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"));
      // 解析 MapperState
      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);
    }
  }

其中,基于 XMLMapperBuilder::buildStatementFromContext(),遍历 <select /><insert /><update /><delete /> 节点们,一一创立 XMLStatementBuilder 对象,执行解析, 通过 XMLStatementBuilder::parseStatementNode() 解析,

  • parameterType
  • resultType
  • selectKey

并会通过 LanguageDriver::createSqlSource()(默认XmlLanguageDriver) 解析动静 sql 生成 SqlSource( 具体内容请看下个大节),

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

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

public void parseStatementNode() {
  // 取得 id 属性,编号
  String id = context.getStringAttribute("id");
  String databaseId = context.getStringAttribute("databaseId");
  // 判断 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");
  // 取得 lang 对应的 LanguageDriver 对象
  LanguageDriver langDriver = getLanguageDriver(lang);
  // 取得 resultType 对应的类
  Class<?> resultTypeClass = resolveClass(resultType);
  String resultSetType = context.getStringAttribute("resultSetType");
  // 取得 statementType 对应的枚举值
  StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  // 取得 resultSet 对应的枚举值
  ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

  String nodeName = context.getNode().getNodeName();
  // 取得 SQL 对应的 SqlCommandType 枚举值
  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);

  // 创立 XMLIncludeTransformer 对象,并替换 <include /> 标签相干的内容
  XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
  includeParser.applyIncludes(context.getNode());

  // 解析 <selectKey /> 标签
  processSelectKeyNodes(id, parameterTypeClass, langDriver);
  
  // 创立 SqlSource 生成动静 sql
  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;
  }
  // 创立 MappedStatement 对象
  this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,                     fetchSize, timeout, parameterMap, parameterTypeClass, 
                    resultMap, resultTypeClass, resultSetTypeEnum, flushCache, 
                    useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, 
                    keyColumn, databaseId, langDriver, resultSets);
}

解析 Mapper 接口中的注解

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

MapperAnnotationBuilder::parse()是注解结构器,负责解析 Mapper 接口上的注解, 解析时须要留神防止和 XMLMapperBuilder::parse() 办法抵触,反复解析,最终应用 parseStatement 解析,那怎么操作?

public void parse() {String resource = type.toString();
  // 判断以后 Mapper 接口是否应加载过。if (!configuration.isResourceLoaded(resource)) {// 加载对应的 XML Mapper, 留神防止和 `XMLMapperBuilder::parse()` 办法抵触
    loadXmlResource();
    // 标记该 Mapper 接口曾经加载过
    configuration.addLoadedResource(resource);
    assistant.setCurrentNamespace(type.getName());
    // 解析 @CacheNamespace 注解
    parseCache();
    parseCacheRef();
     // 遍历每个办法,解析其上的注解
    Method[] methods = type.getMethods();
    for (Method method : methods) {
      try {if (!method.isBridge()) {
          // 执行解析
          parseStatement(method);
        }
      } catch (IncompleteElementException e) {configuration.addIncompleteMethod(new MethodResolver(this, method));
      }
    }
  }
  // 解析待定的办法
  parsePendingMethods();}

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

  • 通过加载 LanguageDriver,GenericTokenParser等为生成 SqlSource 动静 sql 作筹备
  • 应用 MapperBuilderAssistant::addMappedStatement()生成注解 @mapper,@CacheNamespace 等的 MappedStatement 信息
void parseStatement(Method method) {
    // 获取接口参数类型    
    Class<?> parameterTypeClass = getParameterType(method);
    // 加载语言处理器,默认 XmlLanguageDriver
    LanguageDriver languageDriver = getLanguageDriver(method);
    // 依据 LanguageDriver,GenericTokenParser 生成动静 SQL
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    if (sqlSource != null) {
      // 获取其余属性
        Options options = method.getAnnotation(Options.class);
        final String mappedStatementId = type.getName() + "." + method.getName();
        Integer fetchSize = null;
        Integer timeout = null;
        StatementType statementType = StatementType.PREPARED;
        ResultSetType resultSetType = null;
        SqlCommandType sqlCommandType = getSqlCommandType(method);
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        boolean flushCache = !isSelect;
        boolean useCache = isSelect;

        // 取得 KeyGenerator 对象
        KeyGenerator keyGenerator;
        String keyProperty = null;
        String keyColumn = null;
        if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) { // 有
            // first check for SelectKey annotation - that overrides everything else
            // 如果有 @SelectKey 注解,则进行解决
            SelectKey selectKey = method.getAnnotation(SelectKey.class);
            if (selectKey != null) {keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
                keyProperty = selectKey.keyProperty();
            // 如果无 @Options 注解,则依据全局配置解决
            } else if (options == null) {keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
            // 如果有 @Options 注解,则应用该注解的配置解决
            } else {keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
                keyProperty = options.keyProperty();
                keyColumn = options.keyColumn();}
        // 无
        } else {keyGenerator = NoKeyGenerator.INSTANCE;}

        // 初始化各种属性
        if (options != null) {if (FlushCachePolicy.TRUE.equals(options.flushCache())) {flushCache = true;} else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {flushCache = false;}
            useCache = options.useCache();
            fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
            timeout = options.timeout() > -1 ? options.timeout() : null;
            statementType = options.statementType();
            resultSetType = options.resultSetType();}

        // 取得 resultMapId 编号字符串
        String resultMapId = null;
        // 如果有 @ResultMap 注解,应用该注解为 resultMapId 属性
        ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
        if (resultMapAnnotation != null) {String[] resultMaps = resultMapAnnotation.value();
            StringBuilder sb = new StringBuilder();
            for (String resultMap : resultMaps) {if (sb.length() > 0) {sb.append(",");
                }
                sb.append(resultMap);
            }
            resultMapId = sb.toString();
        // 如果无 @ResultMap 注解,解析其它注解,作为 resultMapId 属性
        } else if (isSelect) {resultMapId = parseResultMap(method);
        }
      // 构建 MappedStatement 对象
      assistant.addMappedStatement(
          mappedStatementId,
          sqlSource,
          statementType,
          sqlCommandType,
          fetchSize,
          timeout,
          // ParameterMapID
          null,
          parameterTypeClass,
          resultMapId,
          getReturnType(method),
          resultSetType,
          flushCache,
          useCache,
          // TODO gcode issue #577
          false,
          keyGenerator,
          keyProperty,
          keyColumn,
          // DatabaseID
          null,
          languageDriver,
          // ResultSets
          options != null ? nullOrEmpty(options.resultSets()) : null);
    }
  }

生成动静SqlSource

当在执行 langDriver::createSqlSource(configuration, context, parameterTypeClass) 中的时候,是怎么从 Mapper XML 或办法注解上读取 SQL 内容生成动静 SqlSource 的呢?当初来一探到底,

首先须要获取 langDriver 实现 XMLLanguageDriver/RawLanguageDriver,当初应用默认的XMLLanguageDriver::createSqlSource(configuration, context, parameterTypeClass) 开启创立,再应用 XMLScriptBuilder::parseScriptNode() 解析生成SqlSource

  • DynamicSqlSource:动静的 SqlSource 实现类 , 实用于应用了 OGNL 表达式,或者应用了 ${} 表达式的 SQL
  • RawSqlSource原始 SqlSource 实现类 , 实用于仅应用 #{} 表达式,或者不应用任何表达式的状况
public SqlSource parseScriptNode() {MixedSqlNode rootSqlNode = this.parseDynamicTags(this.context);
        Object sqlSource;
        if (this.isDynamic) {sqlSource = new DynamicSqlSource(this.configuration, rootSqlNode);
        } else {sqlSource = new RawSqlSource(this.configuration, rootSqlNode, this.parameterType);
        }

        return (SqlSource)sqlSource;
    }

那就抉择其中一种来剖析一下 RawSqlSource,怎么实现结构的呢?看看RawSqlSource 构造函数:

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

应用 SqlSourceBuilder::parse() 去解析 SQl,外面又什么神奇的中央呢?

 public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {SqlSourceBuilder.ParameterMappingTokenHandler handler = new SqlSourceBuilder.ParameterMappingTokenHandler(this.configuration, parameterType, additionalParameters);
         // 创立基于 #{}的 GenericTokenParser
        GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
        String sql = parser.parse(originalSql);
        return new StaticSqlSource(this.configuration, sql, handler.getParameterMappings());
    }

ParameterMappingTokenHandlerSqlSourceBuilder 的外部公有动态类, ParameterMappingTokenHandler,负责将匹配到的 #{} 对,替换成相应的 ? 占位符,并获取该 ? 占位符对应的 org.apache.ibatis.mapping.ParameterMapping 对象。

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

总结

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

各位看官还能够吗?喜爱的话,动动手指导个赞???? 呗!!谢谢反对!
欢送扫码关注,原创技术文章第一工夫推出

正文完
 0