美文网首页Java 杂谈
一、mybatis启动分析

一、mybatis启动分析

作者: 丑星星 | 来源:发表于2018-09-12 15:12 被阅读0次

    本专辑将介绍mybatis的原理和源码分析。

    1、概述

    在不使用spring的情况下,我们从官网上可以看到mybatis的使用方法:

    String resource = "org/mybatis/example/mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession session = sqlSessionFactory.openSession();
    try {
      Blog blog = session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
    } finally {
      session.close();
    }
    

    我们可以看到,mybatis启动,首先要加载自己的配置文件,通过配置文件创建SqlSessionFactory,然后再使用SqlSessionFactory对象创建SqlSession对象,通过SqlSession对象来操作数据库。本篇先介绍mybatis加载配置文件的过程。

    2、SqlSessionFactoryBuilder

    首先我们看一下上面例子中SqlSessionFactoryBuilder的build()方法,我们找到这个方法的最终实现:

      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.
          }
        }
      }
    
      public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
      }
    

    我们可以看到默认SqlSessionFactoryBuilder创建SqlSessionFactory,是直接实例化了一个DefaultSqlSessionFactory对象,在创建DefaultSqlSessionFactory对象的时候需要一个Configuration对象作为构造方法的参数,我们也可以看到Configuration对象是由XMLConfigBuilder读取mybatis-config.xml这个配置文件的信息创建的,我们可以看一下具体的创建过程,我们看一下XMLConfigBuilder的parse()方法:

    public Configuration parse() {
        if (parsed) {
          throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
      }
    
      private void parseConfiguration(XNode root) {
        try {
          // 第一步,加载properties节点,把配置文件中定义配置变量解析出来
          propertiesElement(root.evalNode("properties"));
          // 第二步,加载setting节点,
          Properties settings = settingsAsProperties(root.evalNode("settings"));
          loadCustomVfs(settings);
          // 第三步,加载类的别名
          typeAliasesElement(root.evalNode("typeAliases"));
          // 第四步,加载插件
          pluginElement(root.evalNode("plugins"));
         // 第五步,加载objectFactory节点
          objectFactoryElement(root.evalNode("objectFactory"));
          objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
          reflectorFactoryElement(root.evalNode("reflectorFactory"));
          // 第八步,将前面settings节点解析出来的配置设置到Configuration中
          settingsElement(settings);
          // 第九步,加载environments节点,
          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);
        }
      }
    

    我们可以看到,mybatis加载配置文件的时候,以configuration节点作为根节点开始解析,首先,加载properties节点,将properties下定义的变量加载到Configuration的variables属性,具体实现比较简单,这里不看源码,有兴趣的朋友可以自己看一下。
    第二步,解析settings节点,把配置解析成Properties对象,在mybatis运行的过程中,可以通过修改这些属性来改变mybatis 的行为,具体有哪些配置可以参考Mybatis的文档。这里对vfsImpl参数有个特殊处理,可以指定VFS的实现。
    第三步,加载类的别名信息,注册到Configuration的typeAliasRegistry对象中,这个typeAliasRegistry对象中也包含了许多默认的类的别名,如:registerAlias("string", String.class)。注册有两种方式,一种是在<typeAliases></typeAliases>中添加<typeAlias alias="" type=""/>节点,另一种是通过添加<package name="domain.blog"/>直接指定一个包,扫描包中的@Alias("")注解来寻找别名。这些都可以从源码中体现,具体实现源码不再贴出。
    第四步,加载mybatis的插件(Interceptor),这里把定义的插件信息读出来,通过反射创建实例,然后注册到Configuration的interceptorChain中。
    第五步,加载自定义的ObjectFactory,ObjectFactory是mybatis查询出结果后,创建查询结果使用的对象工厂,默认是直接使用目标类的构造方法进行创建(具体实现可以看一下DefaultObjectFactory这个类),这里用户可以自定义实现。这里把用户自定义的ObjectFactory实现类注册到Configuration的objectFactory属性。
    第六步和第七步,分别是加载objectWrapperFactory和reflectorFactory节点,具体过程和加载ObjectFactory节点类似,而且这两个节点在官方文档中没有提及,所以我们大可不必看这两个过程。
    第八步,将前面settings节点解析出来的配置设置到Configuration中。
    第九步,加载environments节点,environments节点下配置了不同环境下的不同的environment节点,environment节点主要配置了transactionManager和dataSource信息。我们可以看一下具体实现:

      private void environmentsElement(XNode context) throws Exception {
        if (context != null) {
          if (environment == null) {
            environment = context.getStringAttribute("default");
          }
          for (XNode child : context.getChildren()) {
            String id = child.getStringAttribute("id");
            if (isSpecifiedEnvironment(id)) {
              TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
              DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
              DataSource dataSource = dsFactory.getDataSource();
              Environment.Builder environmentBuilder = new Environment.Builder(id)
                  .transactionFactory(txFactory)
                  .dataSource(dataSource);
              configuration.setEnvironment(environmentBuilder.build());
            }
          }
        }
      }
    

    我们可以看到,在加载environment的时候,只会去读取default属性指定的environment节点。environment节点下必须要有transactionManager和dataSource节点,不然读取的时候会抛出异常。读取完这些信息之后,会创建Environment对象,并将该对象设置到Configuration的environment属性。
    第十步,加载databaseIdProvider节点,databaseIdProvider的具体作用可以看一下mybatis官方文档,我们可以看一下具体实现:

      private void databaseIdProviderElement(XNode context) throws Exception {
        DatabaseIdProvider databaseIdProvider = null;
        if (context != null) {
          String type = context.getStringAttribute("type");
          // awful patch to keep backward compatibility
          if ("VENDOR".equals(type)) {
              type = "DB_VENDOR";
          }
          Properties properties = context.getChildrenAsProperties();
          databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
          databaseIdProvider.setProperties(properties);
        }
        Environment environment = configuration.getEnvironment();
        if (environment != null && databaseIdProvider != null) {
          String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
          configuration.setDatabaseId(databaseId);
        }
      }
    

    如果定义了databaseIdProvider,mybatis会根据上面environment定义的datasource来选择会用到的databaseId,并设置到configuration的databaseId属性,以供后面加载statements使用。

    第十一步,加载typeHandlers,typeHandlers的功能可以看一下mybatis的官方文档。其作用为:在预处理语句(PreparedStatement)中设置一个参数时,或者从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。解析的最终结果会被注册到Configuration的typeHandlerRegistry中,mybatis定义了很多类型的默认实现,有兴趣的可以看一下源码。typeHandler也可以通过注解的方式定义,这里就不再多说了。
    最后一步是加载mappers节点,我们先来看一下解析mappers节点的源码:

    private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
          for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
              String mapperPackage = child.getStringAttribute("name");
              configuration.addMappers(mapperPackage);
            } else {
              String resource = child.getStringAttribute("resource");
              String url = child.getStringAttribute("url");
              String mapperClass = child.getStringAttribute("class");
              if (resource != null && url == null && mapperClass == null) {
                ErrorContext.instance().resource(resource);
                InputStream inputStream = Resources.getResourceAsStream(resource);
                XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                mapperParser.parse();
              } else if (resource == null && url != null && mapperClass == null) {
                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<?> 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.");
              }
            }
          }
        }
      }
    

    首先我们可以看到,在解析mappers子节点的时候,解析方法会被分成两大类,一种是从xml文件中解析(url、source),一种是从Class解析(class、package)。从Class解析比较简单,仅仅是将Class注册到Configuration的mapperRegistry属性中。而解析xml比较复杂,我们来看一下具体过程,其中url和resource通过xml方式定义mapper(也就是我们平时使用的XXXMapper.xml文件),解析xml定义的mapper是交给XMLMapperBuilder来完成的,我们看一下XMLMapperBuilder的parse()方法:

    public void parse() {
        if (!configuration.isResourceLoaded(resource)) {
          // 加载mapper节点下的所有元素
          configurationElement(parser.evalNode("/mapper"));
          configuration.addLoadedResource(resource);
          bindMapperForNamespace();
        }
    
        parsePendingResultMaps();
        parsePendingCacheRefs();
        parsePendingStatements();
      }
    

    第一步,加载mapper节点下的所有元素,我们看一下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"));
          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. Cause: " + e, e);
        }
      }
    

    首先获取mapper的namespace属性,然后将namespace暂存到builderAssistant中,因为在接下来的过程中会频繁用到namespace属性。
    接下来解析cache-ref和cache属性,这里很简单,就是单纯的解析节点的属性而已,所以不再赘述,如果不知道cache-ref和cache节点定义的作用的读者,建议去官方文档了解一下这两个标签的作用。
    然后是加载parameterMap节点,官方文档已经将这个配置标记为废弃,所以我们可以不用关注这个,我们看一下接下来的解析resultMap的过程。

    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属性(“ofType”、“resultType”、“javaType”),然后是解析extend属性、autoMapping属性。
    接下来就是解析resultMap下的子节点,加载子节点的时候对constructor和discriminator会做特殊处理。constructor和其他的节点一样,都会被解析成一个ResultMapping对象,并加到一个list中。我们可以看一下ResultMapping又那些参数:

      private Configuration configuration;
      private String property;
      private String column;
      private Class<?> javaType;
      private JdbcType jdbcType;
      private TypeHandler<?> typeHandler;
      private String nestedResultMapId;
      private String nestedQueryId;
      private Set<String> notNullColumns;
      private String columnPrefix;
      private List<ResultFlag> flags;
      // 一个<result>节点的column属性中包含多个column的话会被加载成composites
      private List<ResultMapping> composites;
      private String resultSet;
      private String foreignColumn;
      private boolean lazy;
    

    加载逻辑比较简单,这里我们不在赘述,有兴趣的同学可以看一下具体实现。
    我们再看一下加载discriminator节点为Discriminator对象的逻辑:

    private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
        String column = context.getStringAttribute("column");
        String javaType = context.getStringAttribute("javaType");
        String jdbcType = context.getStringAttribute("jdbcType");
        String typeHandler = context.getStringAttribute("typeHandler");
        Class<?> javaTypeClass = resolveClass(javaType);
        @SuppressWarnings("unchecked")
        Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
        JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
        Map<String, String> discriminatorMap = new HashMap<String, String>();
        for (XNode caseChild : context.getChildren()) {
          String value = caseChild.getStringAttribute("value");
          String resultMap = caseChild.getStringAttribute("resultMap", processNestedResultMappings(caseChild, resultMappings));
          discriminatorMap.put(value, resultMap);
        }
        return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap);
      }
    

    这里也很简单,分别读取column、javaType、jdbcType、typeHandler属性,然后读取子节点case节点,把不同的value对应的resultMap解析成一个map,之后创建成一个Discriminator对象返回。
    最后,系统会将resultMap节点解析出来的各种属性封装成一个ResultMap注册到configuration中。到此,resultMap节点的解析我们就看完了。
    我们接下里回到configurationElement()方法中来,在解析完resultMap节点后,接下里会解析sql节点,这里就是生成一个Map,key是sql的id,value是一个XNode节点的对象,解析过程比较简单,我们就不单独看了。
    最后就是解析select、insert、update、delete这些节点了,我们可以看一下buildStatementFromContext()方法的实现,我们一层一层最终,最后可以发现,这些节点是交给XMLStatementBuilder的parseStatementNode()方法来解析的,我们先把源码贴出:

    public void parseStatementNode() {
        String id = context.getStringAttribute("id");
        String databaseId = context.getStringAttribute("databaseId");
    
        if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
          return;
        }
    
        // 此处省略部分代码
    
        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));
    
        // 此处省略部分代码
    
        // 解析节点的子节点
        XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
        includeParser.applyIncludes(context.getNode());
    
        // 解析SelectKey节点
        processSelectKeyNodes(id, parameterTypeClass, langDriver);
        
        // 将节点内容解析成SqlSource
        SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
       
        // 此处省略部分代码
    
        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);
      }
    

    解析这些节点属性的方法都很简单,大家可以看一下源码具体实现,上面的源码省略了这些过程。而解析这些标签子节点的内容这块比较复杂,我们一起来看一下。我们很容易发现,解析节点内容是通过XMLIncludeTransformer的applyIncludes()方法实现的,我们贴出源码来分析:

      public void applyIncludes(Node source) {
        Properties variablesContext = new Properties();
        Properties configurationVariables = configuration.getVariables();
        if (configurationVariables != null) {
          variablesContext.putAll(configurationVariables);
        }
        applyIncludes(source, variablesContext, false);
      }
    
    private void applyIncludes(Node source, final Properties variablesContext, boolean included) {
        if (source.getNodeName().equals("include")) {
          Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext);
          Properties toIncludeContext = getVariablesContext(source, variablesContext);
          applyIncludes(toInclude, toIncludeContext, true);
          if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {
            toInclude = source.getOwnerDocument().importNode(toInclude, true);
          }
          source.getParentNode().replaceChild(toInclude, source);
          while (toInclude.hasChildNodes()) {
            toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);
          }
          toInclude.getParentNode().removeChild(toInclude);
        } else if (source.getNodeType() == Node.ELEMENT_NODE) {
          NodeList children = source.getChildNodes();
          for (int i = 0; i < children.getLength(); i++) {
            applyIncludes(children.item(i), variablesContext, included);
          }
        } else if (included && source.getNodeType() == Node.TEXT_NODE
            && !variablesContext.isEmpty()) {
          // replace variables ins all text nodes
          source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
        }
      }
    

    我们可以看到,这是一个递归方法,当加载的节点是include节点,或者节点属性是ELEMENT_NODE的时候,会递归执行,直到节点属性是TEXT_NODE节点。这里我们需要了解一下java解析xml的一些知识,我们举个例子:
    有一个xml串:

    <a>
      head
      <b></b>
      tail
    <a/>
    

    在解析a标签的子Node的时候,会解析出三个子Node,也就是说 head和tail都会被解析成一个Node,其类型为TEXT_NODE,而<b>节点会被解析成ELEMENT_NODE节点。
    好了,我们再回到mybatis的解析过程中来。我们先看一下TEXT_NODE解析的过程,这个过程只有在included这个参数传入true的时候才会被触发。我们先来看看这个过程做了什么,这个过程是根据我们之前configuration节点下的properties节点解析出的变量,来替换源text文本中的变量(变量以$()这种方式表示),替换过程比较复杂,我们也没必要细看,这里就不多说,我们知道只要知道在解析TEXT_NODE节点的时候会做一个变量替换的过程即可。
    而解析ELEMENT_NODE的过程很简单,就是递归调用而已,我们也不多说。
    最后我们看一下解析include节点,这一步其实也比较简单,就是把当前的include节点替换成include的目标节点。

    我们再回到parseStatementNode()方法,在解析完子节点后,mybatis会处理SelectKey节点,具体方法是:processSelectKeyNodes(),处理过程可以概述为:将SelectKey节点解析成一个MappedStatement对象,然后再将MappedStatement对象封装成SelectKeyGenerator对象,然后根据String id = parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX;这个规则生成一个id作为SelectKeyGenerator的id,注册到configuration对象的keyGenerators属性中,最后再把这个SelectKey节点移除,这里我们不贴出源码了,有兴趣的同学可以自己看一下。

    接下来我们看到mybatis通过这种方式将节点内容解析成对象:

    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    

    LanguageDriver有两个实现类:RawLanguageDriver和XMLLanguageDriver。RawLanguageDriver的注释中写到不推荐使用RawLanguageDriver,除非确定要解析的sql是非动态sql,这里我们只看XMLLanguageDriver的实现就行了。我们一步一步追踪,会发现,创建SqlSource 的过程是交给XMLScriptBuilder类的parseScriptNode()方法实现的,我们来看一下:

      public SqlSource parseScriptNode() {
        List<SqlNode> contents = parseDynamicTags(context);
        MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
        SqlSource sqlSource = null;
        if (isDynamic) {
          sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
        } else {
          sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
        }
        return sqlSource;
      }
    
      List<SqlNode> parseDynamicTags(XNode node) {
        List<SqlNode> contents = new ArrayList<SqlNode>();
        NodeList children = node.getNode().getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
          XNode child = node.newXNode(children.item(i));
          if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
            String data = child.getStringBody("");
            TextSqlNode textSqlNode = new TextSqlNode(data);
            if (textSqlNode.isDynamic()) {
              contents.add(textSqlNode);
              isDynamic = true;
            } else {
              contents.add(new StaticTextSqlNode(data));
            }
          } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
            String nodeName = child.getNode().getNodeName();
            NodeHandler handler = nodeHandlers(nodeName);
            if (handler == null) {
              throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
            }
            handler.handleNode(child, contents);
            isDynamic = true;
          }
        }
        return contents;
      }
    

    我们可以看到,mybatis会将标签的子节点解析成一个SqlNode的List,如果子节点是TEXT_NODE和CDATA_SECTION_NODE,则直接封装成一个TextSqlNode,如果是ELEMENT_NODE节点,说明是动态sql节点,这里使用NodeHandler来生成SqlNode对象。我们可以先来看一下SqlNode有多少子类:


    SqlNode子类

    这些子类正好对应我们使用mybatis动态sql的时候用的节点标签。SqlNode的具体作用我们留到后面分析,我们暂且可以把SqlNode的作用理解为一个sql对象,这个对象可以根据输入内容生成对应的sql语句。看到这里,我们可以猜测一下,在执行动态sql构建的时候,动态sql的构建是根据List<SqlNode>一步一步创建sql拼接而成的。其实我们从接下里的SqlSource构建过程也可以看出,所有的SqlNode节点会被封装成一个MixedSqlNode节点,这个节点算是所有的SqlNode的一个外观节点:

    public class MixedSqlNode implements SqlNode {
      private final List<SqlNode> contents;
    
      public MixedSqlNode(List<SqlNode> contents) {
        this.contents = contents;
      }
    
      @Override
      public boolean apply(DynamicContext context) {
        for (SqlNode sqlNode : contents) {
          sqlNode.apply(context);
        }
        return true;
      }
    }
    

    生成的MixedSqlNode对象会作为SqlSource的构造方法参数被传入SqlSource,到这里,SqlSource的创建过程我们分析完了。我们继续回到上文提到的parseStatementNode()方法,在解析完各种属性,创建完SqlSource对象后,mybatis会根据解析完的参数生成一个MappedStatement对象添加到Configuration对象的mappedStatements属性中,mappedStatements是一个Map,key是节点的id(id属性会被替换成currentNamespace + "." + base)属性,value就是MappedStatement对象。
    从上面的分析,我们看到了mapper节点的解析过程,接下来我们继续回到XMLMapperBuilder的parse()方法,解析完mapper节点之后,mybatis会根据mapper的namespace去寻找对应的Mapper接口,并把接口加载到Configuration的mapperRegistry属性中。这些操作在bindMapperForNamespace()方法中有体现。做完这些动作,parse()方法会调用下面三个方法做一些后续动作,这里我们不再进行详细分析。大该的作用就是将未注册的ResultMap、CacheRefs、Statements注册到Configuration中。

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

    到这里Configuration的创建过程分析完了,大家是不是感到很乱?我把Configuration的几个比较重要的属性加了注释,来方便大家理解:

    public class Configuration {
    
        /**
         * 加载配置文件中的<environment></environment>节点产生的对象,
         * 对象持有transactionFactory和dataSource
         */
        protected Environment environment;
    
        protected boolean safeRowBoundsEnabled;
        protected boolean safeResultHandlerEnabled = true;
        protected boolean mapUnderscoreToCamelCase;
        protected boolean aggressiveLazyLoading;
        protected boolean multipleResultSetsEnabled = true;
        protected boolean useGeneratedKeys;
        protected boolean useColumnLabel = true;
        protected boolean cacheEnabled = true;
        protected boolean callSettersOnNulls;
        protected boolean useActualParamName = true;
        protected boolean returnInstanceForEmptyRow;
    
        protected String logPrefix;
        protected Class <? extends Log> logImpl;
        protected Class <? extends VFS> vfsImpl;
        protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
        protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
        protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
        protected Integer defaultStatementTimeout;
        protected Integer defaultFetchSize;
        protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
        protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
        protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
    
        /**
         * <properties></properties>节点解析出的值
         */
        protected Properties variables = new Properties();
        protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
    
        /**
         * 解析的<objectFactory></objectFactory>节点生成的objectFactory对象,有默认值
         */
        protected ObjectFactory objectFactory = new DefaultObjectFactory();
        protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
    
        protected boolean lazyLoadingEnabled = false;
        protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
    
        /**
         * 根据environment节点下配置的DataSource获取的数据源id
         */
        protected String databaseId;
        /**
         * Configuration factory class.
         * Used to create Configuration for loading deserialized unread properties.
         *
         * @see <a href='https://code.google.com/p/mybatis/issues/detail?id=300'>Issue 300 (google code)</a>
         */
        protected Class<?> configurationFactory;
    
        /**
         * 通过类方式(或者扫描包)方式注册的Mapper类接口和通过xml文件注册的namespace对应的接口
         */
        protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
    
        /**
         * 解析的<plugins></plugins>节点注册的插件
         */
        protected final InterceptorChain interceptorChain = new InterceptorChain();
    
        /**
         * /mapper/parameterMap节点下的typeHandler属性,默认会有一些通用的typeHandler
         */
        protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
        /**
         *<typeAliases></typeAliases>节点解析出来的别名信息
         */
        protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
    
        protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
    
        /**
         * 解析的insert、update、delete、select节点,id为 namespace + "." + id
         */
        protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
        protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
    
        /**
         * 解析的<resultMap></resultMap>节点,id为 namespace + "." + id
         */
        protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
    
        /**
         * 解析的<parameterMap></parameterMap>节点,id为 namespace + "." + id
         */
        protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
    
        /**
         * 解析的insert、update、delete、select节点中的<SelectKey></SelectKey>节点,
         * key是父节点id(insert、update、delete、select这些节点的id)+"!selectKey"
         */
        protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");
    
        /**
         * 已经加载的资源,(xml文件和Class,xml文件添加namespace,Class添加类名)
         */
        protected final Set<String> loadedResources = new HashSet<String>();
        protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");
    
        protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();
        protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>();
        protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>();
        protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>();
    
        /*
         * A map holds cache-ref relationship. The key is the namespace that
         * references a cache bound to another namespace and the value is the
         * namespace which the actual cache is bound to.
         */
        protected final Map<String, String> cacheRefMap = new HashMap<String, String>();
    }
    
    

    SqlSessionFactoryBuilder创建SqlSessionFactory的过程其实最主要是解析配置并生成Configuration对象的过程,创建完Configuration对象之后,SqlSessionFactoryBuilder直接调用DefaultSqlSessionFactory的构造方法来创建SqlSessionFactory对象。

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

    到此我们对Mybatis启动过程中加载配置文件有了一定的了解,后面我们会继续分析mybatis的源码。

    相关文章

      网友评论

        本文标题:一、mybatis启动分析

        本文链接:https://www.haomeiwen.com/subject/ovlegftx.html