mybatis 初始化过程

基于 SqlSession 的使用案例如下:

// 加载全局配置⽂件
InputStream resourceAsStream = Resources.getResourceAsStream("myabtis-config.xml");

// 获得 sqlSession ⼯⼚对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

// 方式一
try (SqlSession session = sqlSessionFactory.openSession()) {
  User user = (User) session.selectOne("com.stydu.mybatis.mapper.UserMapper.findById", 101);
}

// 方式二
try (SqlSession session = sqlSessionFactory.openSession()) {
  UserMapper mapper = session.getMapper(UserMapper.class);
  User user = mapper.findById(101);
}

可以看到,先是通过 Resources.getResourceAsStream("myabtis-config.xml") 得到配置文件流,然后通过 SqlSessionFactoryBuilder.builder 方法得到 SqlSessionFactory 对象,然后就可以使用 sqlSession 提供的方法与数据库交互了。这里先看 SqlSessionFactory 具体怎么得到的

  1. 文件流传入 build 方法

    // org.apache.ibatis.session.SqlSessionFactoryBuilder#build
    public SqlSessionFactory build(InputStream inputStream) {
        return this.build((InputStream)inputStream, (String)null, (Properties)null);
    }
    
  2. 调用重载的 build 方法

    // org.apache.ibatis.session.SqlSessionFactoryBuilder#build
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            // parser.parse() 得到一个 Configuration
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            ...
    } finally {
    		...
        }
        return var5;
    }
    

    从源码可以看出,1 创建了一个 XMLConfigBuilder 对象,2 然后调用了 parse() 方法,返回一个 Configuration 对象,3 根据 Configuration 对象得到 SqlSessionFactory(最终实现类是 DefaultSqlSessionFactory,后面 session 那块专门讲)

    前面说配置文件时有说到:可以配置多个环境,但是 SqlSessionFactory 只能选择一个环境,这里创建 XMLConfigBuilder 对象时,环境对象是空,说明要使用默认的环境

    Configuration 对象就是配置文件和映射文件解析之后得到的对象,看下对象怎么得到的以及这个对象有哪些重要属性

    // 分析这两行代码
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    Configuration configuration = parser.parse();
    
    1. 创建 XMLConfigBuilder 源码如下

      // org.apache.ibatis.builder.xml.XMLConfigBuilder#XMLConfigBuilder
      public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
      	this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
      }
      
      // org.apache.ibatis.parsing.XPathParser#XPathParser
      public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
          commonConstructor(validation, variables, entityResolver);
          // 主要是这里,得到 文件流 的 文档对象
          this.document = createDocument(new InputSource(inputStream));
      }
      
      // org.apache.ibatis.builder.xml.XMLConfigBuilder#XMLConfigBuilder
      private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
          // 创建一个 Configuration 设置给 XMLConfigBuilder 
          super(new Configuration());
          ErrorContext.instance().resource("SQL Mapper Configuration");
          this.configuration.setVariables(props);
          this.parsed = false;
          this.environment = environment;
          // 赋值给 parser,XMLConfigBuilder 也具有了 文档对象
          this.parser = parser;
      }
      

      总结一下,主要信息如下:

      1,先是根据文件流创建一个 XPathParser 对象,这个对象主要包含了文件流的 document 对象;

      2,创建 XMLConfigBuilder 对象,同时初始化一个 Configuration,并设置 parser 为 XPathParser 对象。

      至此 XMLConfigBuilder 包含了一个 Configuration 对象,也具有了配置文件信息,后续就是调用 parser 方法来解析配置文件,并把解析结果设置给 configuration

    2. parse() 方法源码如下:

      // org.apache.ibatis.builder.xml.XMLConfigBuilder#parse
      public Configuration parse() {
          if (parsed) {
      		throw new BuilderException("Each XMLConfigBuilder can only be used once.");
          }
          parsed = true;
          // 先获取 configuration 节点,也就是根节点
          parseConfiguration(parser.evalNode("/configuration"));
          return configuration;
      }
      
      // org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
      private void parseConfiguration(XNode root) {
          try {
              propertiesElement(root.evalNode("properties"));
              Properties settings = settingsAsProperties(root.evalNode("settings"));
              loadCustomVfs(settings);
              loadCustomLogImpl(settings);
              typeAliasesElement(root.evalNode("typeAliases"));
              // 插件
              pluginElement(root.evalNode("plugins"));
              objectFactoryElement(root.evalNode("objectFactory"));
              objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
              reflectorFactoryElement(root.evalNode("reflectorFactory"));
              settingsElement(settings);
              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);
          }
      }
      

      parseConfiguration 调用了一些方法来解析 configuration 标签的各个子标签,这里分析下插件和映射文件怎么解析的

      插件解析

      插件配置在全局配置文件中,配置如下

      <!-- mybatis-config.xml -->
      <plugins>
        <plugin interceptor="org.mybatis.example.ExamplePlugin">
          <property name="someProperty" value="100"/>
        </plugin>
      </plugins>
      

      解析源码

      // org.apache.ibatis.builder.xml.XMLConfigBuilder#pluginElement
      private void pluginElement(XNode parent) throws Exception {
          if (parent != null) {
              // 遍历所有插件,也就是 plugin 标签
              for (XNode child : parent.getChildren()) {
                  // 拿到 plugin 标签的 interceptor 属性
                  String interceptor = child.getStringAttribute("interceptor");
                  // 插件的属性配置,比如上面的 `<property name="someProperty" value="100"/>`
                  Properties properties = child.getChildrenAsProperties();
                  // 反射创建一个拦截器
                  Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
                  // 拿到的属性设置到拦截器中
                  interceptorInstance.setProperties(properties);
                  // 设置到 configuration 属性中
                  configuration.addInterceptor(interceptorInstance);
              }
          }
      }
      
      // org.apache.ibatis.session.Configuration#addInterceptor
      public void addInterceptor(Interceptor interceptor) {
          // 添加到连接器链中
      	interceptorChain.addInterceptor(interceptor);
      }
      

      总结一下,遍历所有插件,一个插件就创建一个拦截器,把所有拦截器保存到 configuration.interceptorChain 属性中

      插件就是拦截器的应用,和 mvc 的 handlerChain 一样也是使用了包装设计模式。这里只分析初始化过程,插件的应用后面专门讲

      映射文件解析

      多种方式配置映射文件,解析的时候也会有多种

      <!-- 使用相对于类路径的资源引用 -->
      <mappers>
        <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
        <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
        <mapper resource="org/mybatis/builder/PostMapper.xml"/>
      </mappers>
      
      <!-- 使用完全限定资源定位符(URL) -->
      <mappers>
        <mapper url="file:///var/mappers/AuthorMapper.xml"/>
        <mapper url="file:///var/mappers/BlogMapper.xml"/>
        <mapper url="file:///var/mappers/PostMapper.xml"/>
      </mappers>
      
      <!-- 使用映射器接口实现类的完全限定类名 -->
      <mappers>
        <mapper class="org.mybatis.builder.AuthorMapper"/>
        <mapper class="org.mybatis.builder.BlogMapper"/>
        <mapper class="org.mybatis.builder.PostMapper"/>
      </mappers>
      
      <!-- 将包内的映射器接口全部注册为映射器 -->
      <mappers>
        <package name="org.mybatis.builder"/>
      </mappers>
      

      解析源码

      // org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement
      private void mapperElement(XNode parent) throws Exception {
          if (parent != null) {
              // 遍历所有子节点
              for (XNode child : parent.getChildren()) {
                  // 如果子节点是 package 标签
                  if ("package".equals(child.getName())) {
                      String mapperPackage = child.getStringAttribute("name");
                      configuration.addMappers(mapperPackage);
                  } else { // 如果不是 package 标签,那就只能是 mapper 标签了
                      // 如果是 mapper 标签,可以配置 resource、url、class(这里感觉可以优化,因为只会三选一)
                      String resource = child.getStringAttribute("resource");
                      String url = child.getStringAttribute("url");
                      String mapperClass = child.getStringAttribute("class");
                      if (resource != null && url == null && mapperClass == null) { // 解析 resource
                          ErrorContext.instance().resource(resource);
                          InputStream inputStream = Resources.getResourceAsStream(resource);
                          // 和全局配置文件类似,这里构建一个 XMLMapperBuilder
                          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                          // 通过 XMLMapperBuilder 的 parser 方法完成解析
                          mapperParser.parse();
                      } else if (resource == null && url != null && mapperClass == null) { // 解析 url
                          ErrorContext.instance().resource(url);
                          InputStream inputStream = Resources.getUrlAsStream(url);
                          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                          mapperParser.parse();
                      } else if (resource == null && url == null && mapperClass != null) { // 解析 class
                          Class<?> mapperInterface = Resources.classForName(mapperClass);
                          configuration.addMapper(mapperInterface);
                      } else {
                          throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                      }
                  }
              }
          }
      }
      
      // org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
      // XMLMapperBuilder 怎么构建就不写了,与 全局配置文件的 XMLConfigurationBuilder 类似,这里看下 parser 方法
      public void parse() {
          if (!configuration.isResourceLoaded(resource)) {
              // 解析 mapper 映射文件
              configurationElement(parser.evalNode("/mapper"));
              configuration.addLoadedResource(resource);
              bindMapperForNamespace();
          }
      
          parsePendingResultMaps();
          parsePendingCacheRefs();
          parsePendingStatements();
      }
      
      // org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement
      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")); // 解析 cache-ref
              cacheElement(context.evalNode("cache")); // 解析 cache
              parameterMapElement(context.evalNodes("/mapper/parameterMap")); // 入参
              resultMapElements(context.evalNodes("/mapper/resultMap")); // 返回对象
              sqlElement(context.evalNodes("/mapper/sql")); // 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);
          }
      }
      
      // org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext
      private void buildStatementFromContext(List<XNode> list) {
          if (configuration.getDatabaseId() != null) {
          	buildStatementFromContext(list, configuration.getDatabaseId());
          }
          buildStatementFromContext(list, null);
      }
      
      // org.apache.ibatis.builder.xml.XMLMapperBuilder#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);
              }
          }
      }
      
      // org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode
      public void parseStatementNode() {
          String id = context.getStringAttribute("id");
          String databaseId = context.getStringAttribute("databaseId");
      
          if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
            return;
          }
          // 解析<select|update|delete|insert>标签属性
          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");
          // 获取LanguageDriver对象
          String lang = context.getStringAttribute("lang");
          LanguageDriver langDriver = getLanguageDriver(lang);
          // 获取 Mapper 返回结果类型 Class 对象
          Class<?> resultTypeClass = resolveClass(resultType);
          String resultSetType = context.getStringAttribute("resultSetType");
          // 默认 Statement 类型为 PREPARED
          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> 标签内容,替换为 <sql> 标签定义的 SQL 片段
          XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
          includeParser.applyIncludes(context.getNode());
      
          // 解析 <selectKey> 标签
          processSelectKeyNodes(id, parameterTypeClass, langDriver);
          
          // 通过 LanguageDriver 解析 SQL 内容,生成 SqlSource 对象
          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 并加入 configuration
          builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
              fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
              resultSetTypeEnum, flushCache, useCache, resultOrdered, 
              keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
      }
      
      // org.apache.ibatis.builder.MapperBuilderAssistant#addMappedStatement
      public MappedStatement addMappedStatement(
              String id,
              SqlSource sqlSource,
              StatementType statementType,
              SqlCommandType sqlCommandType,
              Integer fetchSize,
              Integer timeout,
              String parameterMap,
              Class<?> parameterType,
              String resultMap,
              Class<?> resultType,
              ResultSetType resultSetType,
              boolean flushCache,
              boolean useCache,
              boolean resultOrdered,
              KeyGenerator keyGenerator,
              String keyProperty,
              String keyColumn,
              String databaseId,
              LanguageDriver lang,
              String resultSets) {
      
          if (unresolvedCacheRef) {
              throw new IncompleteElementException("Cache-ref not yet resolved");
          }
      	
          // id 改为 namespace + id
          id = applyCurrentNamespace(id, false);
          boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
      
          // 构建 MappedStatement,建造者模式
          MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
                  .resource(resource)
                  .fetchSize(fetchSize)
                  .timeout(timeout)
                  .statementType(statementType)
                  .keyGenerator(keyGenerator)
                  .keyProperty(keyProperty)
                  .keyColumn(keyColumn)
                  .databaseId(databaseId)
                  .lang(lang)
                  .resultOrdered(resultOrdered)
                  .resultSets(resultSets)
                  .resultMaps(getStatementResultMaps(resultMap, resultType, id))
                  .resultSetType(resultSetType)
                  .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
                  .useCache(valueOrDefault(useCache, isSelect))
                  .cache(currentCache);
      
          ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
          if (statementParameterMap != null) {
              statementBuilder.parameterMap(statementParameterMap);
          }
      	
          // MappedStatement 构建完成
          MappedStatement statement = statementBuilder.build();
          
          // 加入 configuration
          configuration.addMappedStatement(statement);
          return statement;
      }
      
      // org.apache.ibatis.session.Configuration#addMappedStatement
      public void addMappedStatement(MappedStatement ms) {
          // 终于维护完成,真 tn 长
      	mappedStatements.put(ms.getId(), ms);
      }
      

      需要注意:并没有注册 MapperRegistry,因为这是传统的 mybatis 的初始化过程,spring + mybatis 的项目就会注册 MapperRegistry,后面专门讲(其实底层还是一样的,多了一步扫描 mapper 接口的步骤而已)

posted @ 2023-07-21 11:17  黄光跃  阅读(17)  评论(0编辑  收藏  举报