• Mybatis源码解析,一步一步从浅入深(五):mapper节点的解析


    在上一篇文章Mybatis源码解析,一步一步从浅入深(四):将configuration.xml的解析到Configuration对象实例中我们谈到了properties,settings,environments节点的解析,总结一下,针对示例工程的configuration.xml文件来说properties节点的解析就是将dbConfig.properties中的数据库配置信息加载到了configuration实例的variables中,settings节点的解析让configuration使用了我们配置的log4j日志系统,environments节点的解析生成了数据库环境类(Environment)的实例对象,并将这个示例对象赋值给了 configuration的environment属性。那么接下来我们着重看一下mappers节点的解析。mappers节点的解析非常重要,所以本文篇幅会很长。

    一,先看看示例工程的mappers节点和userDao-mapping.xml文件

      mappers节点:

        

    <!-- 映射文件,mybatis精髓 -->
      <mappers>
        <mapper resource="mapper/userDao-mapping.xml"/>
      </mappers>

      userDao-mapping.xml文件:

    <?xml version="1.0" encoding="UTF-8" ?>   
    <!DOCTYPE mapper   
    PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"  
    "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd"> 
    <mapper namespace="com.zcz.learnmybatis.dao.UserDao">
    
       <select id="findUserById" resultType="com.zcz.learnmybatis.entity.User" > 
          select * from user where id = #{id}
       </select>
    
    </mapper>

      userDao-mapping.xml文件很简单,定义了一个namespace属性指向UserDao接口,定义了一个select标签声明。

    二,看一下解析mappers节点的方法mapperElement的源码:

      

    private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
          for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
              //检测是否是package节点
              String mapperPackage = child.getStringAttribute("name");
              configuration.addMappers(mapperPackage);
            } else {
              //读取<mapper resource="mapper/userDao-mapping.xml"/>中的mapper/userDao-mapping.xml,即resource = "mapper/userDao-mapping.xml"
              String resource = child.getStringAttribute("resource");
              //读取mapper节点的url属性
              String url = child.getStringAttribute("url");
              //读取mapper节点的class属性
              String mapperClass = child.getStringAttribute("class");
              if (resource != null && url == null && mapperClass == null) {
                //根据rusource加载mapper文件
                ErrorContext.instance().resource(resource);
                //读取文件字节流
                InputStream inputStream = Resources.getResourceAsStream(resource);
                //实例化mapper解析器
                XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                //执行解析mapper文件,即解析mapper/userDao-mapping.xml,文件
                mapperParser.parse();
              } else if (resource == null && url != null && mapperClass == null) {
                //从网络url资源加载mapper文件
                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) {
                //使用mapperClass加载文件
                Class<?> mapperInterface = Resources.classForName(mapperClass);
                configuration.addMapper(mapperInterface);
              } else {
                //resource,url,mapperClass三种配置方法只能使用其中的一种,否则就报错
                throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
              }
            }
          }
        }
      }

      一目了然,遍历mappers中的mapper节点,然后逐一解析。我们的配置文件中只有一个mapper节点,所以这里要解析的就是mapper/userDao-mapping.xml文件。

      解析mapper/userDao-mapping.xml文件的关键代码是

        //实例化mapper解析器
        XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
        //执行解析mapper文件,即解析mapper/userDao-mapping.xml,文件
        mapperParser.parse();

      接下来逐句进行分析

    三,实例化mapper解析器:XMLMapperBuilder 

      代码:XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());

      查看mapper解析器XMLMapperBuilder类的声明可以发现,mapper解析器类XMLMapperBuilder和xml配置解析器XMLConfigBuilder同时继承了父类BaseBuilder。其实后面还有几个类也继承了父类BaseBuilder。

      

    public class XMLMapperBuilder extends BaseBuilder {}

      看一下使用到的XMLMapperBuilder构造方法

        

    public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
        this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
            configuration, resource, sqlFragments);
      }

      这里也创建了一个XPathParser xml文件解析器,原因很简单,因为mapper/userDao-mapping.xml也是一个xml文件(手动捂脸)。至于最后一个参数 Map<String, XNode> sqlFragments 是什么?现在只知道sqlFragments = configuration.getSqlFragments(),但是具体是什么呢?稍后纤细介绍。

      接下来使用this关键字,调用了XMLMapperBuilder的私有构造方法:

      

    private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
        super(configuration);
        this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
        this.parser = parser;
        this.sqlFragments = sqlFragments;
        this.resource = resource;
      }

      看过Mybatis源码解析,一步一步从浅入深(三):实例化xml配置解析器(XMLConfigBuilder)的同学想必已经知道supper()方法做了什么,这里就不再赘述了。值得一提的是MapperBuilderAssistant(mapper解析器助手),既然是助手,那肯定就是辅助mapper解析器解析mapper文件的了。

      

    public class MapperBuilderAssistant extends BaseBuilder {}

      看看MapperBuilderAssistant类的声明,它也是继承了BaseBuilder的。

      到这里mapper解析器(XMLMapperBuilder)的实例化工作就已经完成了。但是为了更好了进行接下来的分析,我们有必要再认识Configuration类的一些属性和方法:

    public class Configuration {
    
      protected Environment environment;
      protected Properties variables = new Properties();
      ......
      //初始化值为null
      protected String databaseId;
      protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
      protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
    
      // 这是一个HashMap ,存放的是已经解析过的sql声明,String  类型的键,例如com.zcz.learnmybatis.entity.User.findUserById,值是MappedStatement实例对象
      protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
      protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");
      ......
      //这是一个无序不重复的Set集合,里面存放的是已经加载解析过的 mapper文件名。例如<mapper resource="mapper/userDao-mapping.xml"/>中的mapper/userDao-mapping.xml
      protected final Set<String> loadedResources = new HashSet<String>();
      ......
      //sql碎片Map,键String 值XNode,这个Map中存放的是已经在先前的mapper中解析过的碎片
      protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");
      ......
      public Configuration() {
        .....
        // 注册默认的XML语言驱动
        languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
        languageRegistry.register(RawLanguageDriver.class);
      }
      ......
      //将resource 添加到 加载解析完成Set loadedResources中
      public void addLoadedResource(String resource) {
        loadedResources.add(resource);
      }
      //检测mapper文件是否已经被加载解析过,resource是<mapper resource="mapper/userDao-mapping.xml"/>中的resource
      public boolean isResourceLoaded(String resource) {
        return loadedResources.contains(resource);
      }
    
      ......
     
      //根据标签声明类(MappedStatement) 实例对象的id获取 解析过的标签声明类实例对象
    // 标签声明是什么,在下文中会给出解释 public MappedStatement getMappedStatement(String id) { return this.getMappedStatement(id, true); } //根据标签声明类(MappedStatement) 实例对象的id获取 解析过的标签声明类实例对象 public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) { if (validateIncompleteStatements) { buildAllStatements(); } return mappedStatements.get(id); } //获取sql碎片 public Map
    <String, XNode> getSqlFragments() { return sqlFragments; } ...... // 根据检查是否存在 标签声明名称 为statementName 的标签声明 public boolean hasStatement(String statementName) { return hasStatement(statementName, true); } // 根据检查是否存在 标签声明名称 为statementName 的标签声明 public boolean hasStatement(String statementName, boolean validateIncompleteStatements) { if (validateIncompleteStatements) { buildAllStatements(); } return mappedStatements.containsKey(statementName); } ...... }

    四,执行解析mapper文件,即解析mapper/userDao-mapping.xml文件

      代码:mapperParser.parse();

      看一下负责解析mapper文件的parser方法的源代码:

      public void parse() {
        // 先判断mapper文件是否已经解析
        if (!configuration.isResourceLoaded(resource)) {
          //执行解析
          configurationElement(parser.evalNode("/mapper"));
          //保存解析记录
          configuration.addLoadedResource(resource);
          // 绑定已经解析的命名空间
          bindMapperForNamespace();
        }
    
        ......
      }

      很明显,真正解析mapper文件的代码是configurationElement方法:

      

    private void configurationElement(XNode context) {
        try {
          //获取mapper文件中的mapper节点的namespace属性 com.zcz.learnmybatis.entity.UserDao
          String namespace = context.getStringAttribute("namespace");
          if (namespace.equals("")) {
              throw new BuilderException("Mapper's namespace cannot be empty");
          }
          //将namespace赋值给映射 mapper解析器助理builderAssistant.currentNameSpace,即告诉mapper解析器助理现在解析的是那个mapper文件
          builderAssistant.setCurrentNamespace(namespace);
          //解析cache-ref节点
          cacheRefElement(context.evalNode("cache-ref"));
          //解析cache节点
          cacheElement(context.evalNode("cache"));
          //解析parameterMap节点,这里为什么要使用"/mapper/parameterMap"而不是直接使用"parameterMap",因为parameterMap可以配置多个,而且使用的是context.evalNodes方法,注意不是evalNode了,是evalNodes。
          parameterMapElement(context.evalNodes("/mapper/parameterMap"));
          //解析resultMap节点
          resultMapElements(context.evalNodes("/mapper/resultMap"));
          //解析sql节点
          sqlElement(context.evalNodes("/mapper/sql"));
          //解析select|insert|update|delete节点,注意context.evalNodes()方法,返回的是一个List集合。
          buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
        }
      }

      从上面代码看到,处理完namespace之后,就是解析mapper文件中的节点了,但是我们的userDao-mapping.xml文件中只有一个select标签:

        

    <select id="findUserById" resultType="com.zcz.learnmybatis.entity.User" > 
          select * from user where id = #{id}
       </select>

      那么只需要分析最有一个方法buildStatementFromContext就可以了。

      看源码:

      这个方法中的唯一的参数list 就是userDao-mapping.xml中的select,update,delete,insert标签们。注意是一个List。也就是说可能会有多个。

     private void buildStatementFromContext(List<XNode> list) {
       //这里的confuration.getDatabaseId 是 null ,因为 configuration初始化时没有给默认值,在虚拟机实例化configuration对象时,赋予默认值null
    if (configuration.getDatabaseId() != null) { buildStatementFromContext(list, configuration.getDatabaseId()); } buildStatementFromContext(list, null); }

      又调用了buildStatementFromContext 重载方法:

      在这个方法中遍历了上面我们提到的list.而我们的userDao-mapping.xml中的select标签就是在这个list中。

      我们都知道,在mapper文件中,select标签,update标签,delete标签,insert标签的id属性对应着namespace中的接口的方法名。所以我们就称一个select标签,update标签,delete标签或者一个insert标签为一个标签声明。

      那么这个方法中就是遍历了所有的标签声明,并逐一解析。

    private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
        for (XNode context : list) {
        //初始化标签声明解析器(XMLStatementBuilder)
    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try {
          // 执行标签声明的解析 statementParser.parseStatementNode(); }
    catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } } }

      到现在我们终于明白,原来buildStatementFromContext 也是不负责解析的,真正负责解析的是final 修饰的 XMLStatementBuilder类 的实例对象 statementParser。

      

    public class XMLStatementBuilder extends BaseBuilder {}

      发现了什么?

      XMLStatementBuilder也是继承BaseBuilder的。

      看看构造方法:

      

      public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, XNode context, String databaseId) {
        super(configuration);
        this.builderAssistant = builderAssistant;
        this.context = context;
        this.requiredDatabaseId = databaseId;
      }

      只有一些赋值操作。

      重点就是try-catch块中的执行标签声明的解析的:statementParser.parseStatementNode();这一句代码了。

      看源码:

        

     1 public void parseStatementNode() {
     2     // 获取的是select 标签的id属性,即id="findUserById"
     3     String id = context.getStringAttribute("id");
     4     // 没有databaseId属性,即databaseId = null;
     5     String databaseId = context.getStringAttribute("databaseId");
     6    // 判断databaseId,这一行代码下方有详细介绍 
     7     if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) return;
     8     // 没有fetchSize属性,即fetchSize = null;
     9     Integer fetchSize = context.getIntAttribute("fetchSize");
    10     // 没有timeout属性,即timeout = null;
    11     Integer timeout = context.getIntAttribute("timeout");
    12     // 没有 parameterMap 属性,即 parameterMap = null;
    13     String parameterMap = context.getStringAttribute("parameterMap");
    14     // 没有 parameterType 属性,即 parameterType = null;
    15     String parameterType = context.getStringAttribute("parameterType");
    16     // parameterType = null,即parameterTypeClass = null
    17     Class<?> parameterTypeClass = resolveClass(parameterType);
    18     // 没有 resultMap 属性,即 resultMap = null;
    19     String resultMap = context.getStringAttribute("resultMap");
    20     // 获取的是select 标签的resultType属性,即resultType="com.zcz.learnmybatis.entity.User"
    21     String resultType = context.getStringAttribute("resultType");
    22     // 没有 lang 属性,即 lang = null;
    23     String lang = context.getStringAttribute("lang");
    24     //获取默认的语言驱动 XMLLanguageDriver
    25     LanguageDriver langDriver = getLanguageDriver(lang);
    26 
    27     // 获取User类的类对象
    28     Class<?> resultTypeClass = resolveClass(resultType);
    29     // 没有 resultSetType 属性,即 resultSetType = null;
    30     String resultSetType = context.getStringAttribute("resultSetType");
    31     // 没有 statementType 属性,返回默认的 “PREPARED” 即statementType = PREPARED
    32     StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    33     // 没有 resultSetType 属性,即 resultSetType = null;
    34     ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    35     
    36     // nodeName = "select"
    37     String nodeName = context.getNode().getNodeName();
    38     // sql类型是 sqlCommandType = SELECT
    39     SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    40     // isSelect = true;
    41     boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    42     // 没有 flushCache 属性,即 flushCache = null; 取默认flushCache = !isSelect = false;
    43     boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    44     // 没有 useCache 属性,即 useCache = null; 取默认useCache = isSelect= true;
    45     boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    46     // 没有 resultOrdered 属性,即 resultOrdered = null; 取默认resultOrdered= false;
    47     boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
    48 
    49     // Include Fragments before parsing
    50     // 处理include 标签,我们的select标签中没有用到include标签
    51     XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    52     includeParser.applyIncludes(context.getNode());
    53 
    54     // Parse selectKey after includes and remove them.
    55     // 处理selectKey 标签,我们的select标签中没有用到selectKey标签
    56     processSelectKeyNodes(id, parameterTypeClass, langDriver);
    57 
    58     // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    59     // 在解析完<selectKey> 和 <include> 之后开始解析 SQL语句
    60     SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    61     // 没有 resultSets 属性,即 resultSets = null;
    62     String resultSets = context.getStringAttribute("resultSets");
    63     // 没有 keyProperty 属性,即 keyProperty = null;
    64     String keyProperty = context.getStringAttribute("keyProperty");
    65     // 没有 keyColumn 属性,即 keyColumn = null;
    66     String keyColumn = context.getStringAttribute("keyColumn");
    67     
    68     //接下来处理的是主键生成器
    69     KeyGenerator keyGenerator;
    70     String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    71     keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    72     if (configuration.hasKeyGenerator(keyStatementId)) {
    73       keyGenerator = configuration.getKeyGenerator(keyStatementId);
    74     } else {
    75       // 应为我们的是select类型的语句,所以SqlCommandType.INSERT.equals(sqlCommandType) == false。所以keyGenerator = new NoKeyGenerator()
    76       keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
    77           configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
    78           ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
    79     }
    80     
    81     // 这一步 就是让mapper解析器助理创建MappedStatement实例对象,并将新建的实例对象添加到 configuration的 mappedStatements中,表示这个标签声明被解析过了。
    82     //根据上方的解析过程,我们可以清晰的知道各个参数的值:
    83     //id = "findUserById",sqlSource = RawSqlSource 实例对象,statementType = PREPARED,sqlCommandType = SELECT
    84     //fetchSize,timeout,parameterMap,parameterTypeClass,resultMap= null
    85     //resultTypeClass = User类对象
    86     //resultSetTypeEnum = null
    87     //flushCache=false,useCache=true,resultOrdered=false,keyGenerator = new NoKeyGenerator()
    88     //keyProperty, keyColumn, databaseId,=null
    89     //langDriver=XMLLanguageDriver实例对象
    90     //resultSets=null。
    91     builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
    92         fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
    93         resultSetTypeEnum, flushCache, useCache, resultOrdered, 
    94         keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
    95 }

      通过代码中的注释,相信大家都能看的明白,这里着重解释一下,第7行,第60行,第91行:

      第7行:if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) return;中的databaseIdMatchesCurrent方法源码:  

     1 //比较需要使用的databaseId 和 标签声明中的databaseId 是否相同,这时id="findUserById",同时databaseId和requiredDatabaseId 都是null
     2   private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
     3     if (requiredDatabaseId != null) {
     4       if (!requiredDatabaseId.equals(databaseId)) {
     5         //如果不同就返回false,停止解析
     6         return false;
     7       }
     8     } else {
     9       if (databaseId != null) {
    10         // 这个时候requiredDatabaseId == null, 在这个requiredDatabaseId 等于 null的情况下databaseId 却不等于null,说明需要使用的databaseId 和 标签声明中的databaseId 是不相同的,就放回false,停止解析
    11         return false;
    12       }
    13       // skip this statement if there is a previous one with a not null databaseId 如果存在已经解析过的并且databaseId不为null的标签声明,则返回false跳过解析
    14       // 获取id,这个id就是标签解析器的id,从接下来的分析中可以明确看出:这个id也是标签声明类(MappedStatement)实例化对象的id。
    15       id = builderAssistant.applyCurrentNamespace(id, false);
    16       
    17       if (this.configuration.hasStatement(id, false)) {
    18         MappedStatement previous = this.configuration.getMappedStatement(id, false); // issue #2
    19         if (previous.getDatabaseId() != null) {
    20           return false;
    21         }
    22       }
    23     }
    24     return true;
    25   }

      而源码中的applyCurrentNamespace源码是:

        

    public String applyCurrentNamespace(String base, boolean isReference) {
        if (base == null) return null;
        if (isReference) {
          // is it qualified with any namespace yet?
          if (base.contains(".")) return base;
        } else {
          // is it qualified with this namespace yet?
          if (base.startsWith(currentNamespace + ".")) return base;
          if (base.contains(".")) throw new BuilderException("Dots are not allowed in element names, please remove it from " + base);
        }
        // 返回com.zcz.learnmybatis.entity.UserDao.findUserById
        // currentNamespace 在前面已经设置过了,就是mapper文件中的namespace
        return currentNamespace + "." + base;
      }

      第60行是用来处理SQL语句的,也就是用来处理:

        

     select * from user where id = #{id}

      这一部分的,处理了什么呢?简单来说就是根据”${“是否存在来判断SQL语句是否是动态SQL语句,并且把 select * from user where id = #{id} 转换为select * from user where id = ?。同时把#{id}中的id保存起来。具体细节源码不展开了,需要的话,再写一篇文章详细解析吧。

      第91行,就是把一个select ,update,delete 或者insert标签声明转换为MappedStatement对象实例,更明白点说,就是把

      

    <select id="findUserById" resultType="com.zcz.learnmybatis.entity.User" > 
          select * from user where id = #{id}
       </select>

      这一部分转换为MappedStatement对象并保持到configuration中,实现保存的代码源码如下:

      但是要注意下面代码里的id = "findUserById",但是经过applyCurrentNameSpace()方法后,id= "com.zcz.learnmybatis.dao.UserDao.findUserById",即在原来的id前,添加了UserDao类全包名+类名+“.”;

    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 = applyCurrentNamespace(id, false);
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
      //初始化MappenStatement.Builder
        MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType);
        statementBuilder.resource(resource);
        statementBuilder.fetchSize(fetchSize);
        statementBuilder.statementType(statementType);
        statementBuilder.keyGenerator(keyGenerator);
        statementBuilder.keyProperty(keyProperty);
        statementBuilder.keyColumn(keyColumn);
        statementBuilder.databaseId(databaseId);
        statementBuilder.lang(lang);
        statementBuilder.resultOrdered(resultOrdered);
        statementBuilder.resulSets(resultSets);
        setStatementTimeout(timeout, statementBuilder);
    
        setStatementParameterMap(parameterMap, parameterType, statementBuilder);
        setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);
        setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder);
      // 构造MappedStatement
        MappedStatement statement = statementBuilder.build();
      // 保存 configuration.addMappedStatement(statement);
    return statement; }

      值得一提的是在MappedStatement statement = statementBuilder.build();我们先看看源码:

      

    1 public MappedStatement build() {
    2       assert mappedStatement.configuration != null;
    3       assert mappedStatement.id != null;
    4       assert mappedStatement.sqlSource != null;
    5       assert mappedStatement.lang != null;
    6       mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps);
    7       return mappedStatement;
    8     }

      在第六行有一个Collections.unmodifiableList方法,这个方法是一个很有趣的方法,想要了解一下的话,请查阅:Collections.unmodifiableMap,Collections.unmodifiableList,Collections.unmodifiableSet作用及源码解析

      

    到这里 select的标签声明的解析就结束了,同时Mapper文件的解析也结束了。

    总结一下,mappers节点解析完成之后,所有的mybatis有关的配置文件都已经解析完成了,包括:configuration.xml文件,dbConfig.properties文件,userDa0-mapping.xml文件。并且都保存到Configuration 的实例化对象中了。

        


     原创不易,转载请声明出处:https://www.cnblogs.com/zhangchengzi/p/9682487.html 

  • 相关阅读:
    (转)正则表达式与Python(RE)模块
    (转)【面试】【MySQL常见问题总结】【03】
    (转)MySQL性能调优my.cnf详解
    (转)python logging模块
    (转)python collections模块详解
    mysql故障总结
    rocksdb 编译安装 日志
    c++11 gcc4.8.x安装
    Install ssdb-rocks on CentOS 6
    在Shell里面判断字符串是否为空
  • 原文地址:https://www.cnblogs.com/zhangchengzi/p/9682487.html
Copyright © 2020-2023  润新知