• 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 

  • 相关阅读:
    file is universal (3 slices) but does not contain a(n) armv7s slice error for static libraries on iOS
    WebImageButton does not change images after being enabled in Javascript
    ajax OPTION
    编程遍历页面上所有TextBox控件并给它赋值为string.Empty?
    获取海洋天气预报
    C#线程系列教程(1):BeginInvoke和EndInvoke方法
    js控制只能输入数字和小数点
    Response.AddHeader(,)
    ManualResetEvent的理解
    Convert.ToInt32、int.Parse(Int32.Parse)、int.TryParse、(int) 区别
  • 原文地址:https://www.cnblogs.com/zhangchengzi/p/9682487.html
Copyright © 2020-2023  润新知