该系列文章收录在公众号【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供应用户进行操作。

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