Mybatis源码学习(22)-Mapper映射文件中个元素的解析过程

一、Mapper映射文件结构

  根据定义Mapper映射文件结构的mybatis-3-mapper.dtd文件,可以知道Mapper映射文件的直接子元素有:

  • cache – 对给定命名空间的缓存配置。
  • cache-ref – 对其他命名空间缓存配置的引用。
  • resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
  • parameterMap – 已被废弃!老式风格的参数映射。更好的办法是使用内联参数,此元素可能在将来被移除。文档中不会介绍此元素。
  • sql – 可被其他语句引用的可重用语句块。
  • insert – 映射插入语句
  • update – 映射更新语句
  • delete – 映射删除语句
  • select – 映射查询语句

  mybatis-3-mapper.dtd文件中定义顶级元素的约束如下,包括了直接子元素,根元素的属性等:

<!ELEMENT mapper (cache-ref | cache | resultMap* | parameterMap* | sql* | insert* | update* | delete* | select* )+>
<!ATTLIST mapper
namespace CDATA #IMPLIED
>
二、Mapper文件解析入口

  在前面《Mybatis中如何解析所有配置的Mapper映射文件》中已经分析了从Mybatis配置文件中解析<mappers>元素,然后再解析<mapper>或<package>子元素。在解析 <mapper>或<package>子元素的过程中,当加载XML类型的映射时,就会解析每个XML类型映射文件中的所有子元素。

  首先是通过XMLMapperBuilder的parse()方法,进入解析每个<mapper>映射文件的方法,代码如下所示:

//XMLMapperBuilder.java
public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

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

  在上述的parse()方法中,通过调用configurationElement()方法来处理<mapper>元素中所有的子元素,主要包括了cache-ref 、cache 、 resultMap、 parameterMap、sql、insert、update、delete、select等,首先判断根节点的命名空间,如果为空,直接抛出异常,说明根节点的namespace属性是必须的,然后在通过调用各自的方法,处理前面提到的相应节点。代码如下所示:

//XMLMapperBuilder.java
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. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }
三、缓存(cache、cache-ref )
1、使用

  MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。
  默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:

<cache/>

  基本上就是这样。这个简单语句的效果如下:

  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

提示: 缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。

  这些属性可以通过 cache 元素的属性来修改。比如:

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

  这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

  可用的清除策略有:

  • LRU – 最近最少使用:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

  默认的清除策略是 LRU。

  flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
  size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
  readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。

提示: 二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。

更多缓存相关用法,请参考《官方文档-XML 映射文件-缓存》

2、缓存元素的解析过程

  通过上述Mapper映射文件结构可以知道,配置缓存的元素有<cache>、<cache-ref>两种。其中,<cache>子元素用于对给定命名空间的缓存配置;<cache-ref>子元素对其他命名空间缓存配置的引用。
  在XMLMapperBuilder的configurationElement()方法中可以知道,解析缓存的方法有cacheRefElement()、cacheElement()两个,分别对应了解析<cache-ref>和<cache>两种元素。

  1. <cache>元素结构
    通过Mapper文件结构定义文档mybatis-3-mapper.dtd中关于<cache>元素配置结构,可以知道,<cache>元素可以有type、eviction 、flushInterval 、size 、readOnly 、blocking 等属性或者子元素<property>。

    <!ELEMENT cache (property*)>
    <!ATTLIST cache
    type CDATA #IMPLIED
    eviction CDATA #IMPLIED
    flushInterval CDATA #IMPLIED
    size CDATA #IMPLIED
    readOnly CDATA #IMPLIED
    blocking CDATA #IMPLIED
    >
    
  2. <cache>元素解析
    解析<cache>元素的逻辑由cacheElement()方法实现。该方法,首先解析了该元素可能有的所有属性值或者子元素<property>,然后通过MapperBuilderAssistant的useNewCache()方法创建对应的缓存Cache实例,并把该缓存实例对象存储到了全局变量configuration的属性caches中。

  XMLMapperBuilder的cacheElement()方法,主要实现解析<cache>元素所有属性值或者子元素<property>。

//XMLMapperBuilder.java
private void cacheElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type", "PERPETUAL");
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
      String eviction = context.getStringAttribute("eviction", "LRU");
      Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
      Long flushInterval = context.getLongAttribute("flushInterval");
      Integer size = context.getIntAttribute("size");
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      boolean blocking = context.getBooleanAttribute("blocking", false);
      Properties props = context.getChildrenAsProperties();
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }

  MapperBuilderAssistant的useNewCache()方法主要实现了根据<cache>元素配置的相关属性,实现创建缓存Cache实例,并把该缓存实例对象存储到了全局变量configuration的属性caches中。

//MapperBuilderAssistant.xml
  public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
  }
  1. <cache-ref>元素结构
    通过Mapper文件结构定义文档mybatis-3-mapper.dtd中关于<cache-ref>元素配置结构,可以知道,<cache-ref>元素有且只有一个属性namespace。

    <!ELEMENT cache-ref EMPTY>
    <!ATTLIST cache-ref
    namespace CDATA #REQUIRED
    >
    
  2. <cache-ref>元素解析
    解析<cache-ref>元素的逻辑由cacheRefElement()方法实现。该方法,首先解析了该元素的namespace属性,然后把该元素的namespace属性值和当前命名空间的字符串存储到全局唯一实例对象configuration属性的cacheRefMap中。然后通过CacheRefResolver 实例对象的resolveCacheRef()方法实现元素的解析,在resolveCacheRef()方法中实际上是通过MapperBuilderAssistant对象的useCacheRef()方法实现。

  XMLMapperBuilder的cacheRefElement()方法,主要实现解析<cache-ref>元素的namespace属性,然后并创建CacheRefResolver 实例对象,再通过CacheRefResolver 对象的resolveCacheRef()方法实现元素的解析。

//XMLMapperBuilder.java
  private void cacheRefElement(XNode context) {
    if (context != null) {
      configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
      CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
      try {
        cacheRefResolver.resolveCacheRef();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteCacheRef(cacheRefResolver);
      }
    }
  }

  CacheRefResolver 类的构造函数,还有resolveCacheRef()方法。在resolveCacheRef()方法中,实际上就是通过MapperBuilderAssistant 的useCacheRef()方法解析创建对应缓存对象。

//CacheRefResolver.java
  public CacheRefResolver(MapperBuilderAssistant assistant, String cacheRefNamespace) {
    this.assistant = assistant;
    this.cacheRefNamespace = cacheRefNamespace;
  }

  public Cache resolveCacheRef() {
    return assistant.useCacheRef(cacheRefNamespace);
  }

  和解析<cache>元素一样,解析<cache-ref>元素最终是由MapperBuilderAssistant 的useCacheRef()方法实现,这里不会创建新的缓存Cache,而是根据当前的namespace获取已经存在的Cache对象。

public Cache useCacheRef(String namespace) {
    if (namespace == null) {
      throw new BuilderException("cache-ref element requires a namespace attribute.");
    }
    try {
      unresolvedCacheRef = true;
      Cache cache = configuration.getCache(namespace);
      if (cache == null) {
        throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
      }
      currentCache = cache;
      unresolvedCacheRef = false;
      return cache;
    } catch (IllegalArgumentException e) {
      throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
    }
  }
四、parameterMap元素解析

  已被废弃!老式风格的参数映射。更好的办法是使用内联参数,此元素可能在将来被移除。文档中不会介绍此元素。

五、resultMap元素解析

  resultMap是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。过于复杂,后续单独章节分析该元素的解析过程。

六、sql元素解析

  sql元素用来定义可被其他语句引用的可重用语句块。

  1. SQL元素的结构
    通过Mapper文件结构定义文档mybatis-3-mapper.dtd中关于<sql>元素配置结构,可以知道,<sql>元素可以有id、lang、databaseId三个属性,其中id属性是必须;还可以有include、trim、where、set、foreach、choose、if、bind等子元素。
<!ELEMENT sql (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
<!ATTLIST sql
id CDATA #REQUIRED
lang CDATA #IMPLIED
databaseId CDATA #IMPLIED
>

<!ELEMENT trim (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
<!ATTLIST trim
prefix CDATA #IMPLIED
prefixOverrides CDATA #IMPLIED
suffix CDATA #IMPLIED
suffixOverrides CDATA #IMPLIED
>
<!ELEMENT where (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
<!ELEMENT set (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>

<!ELEMENT foreach (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
<!ATTLIST foreach
collection CDATA #REQUIRED
item CDATA #IMPLIED
index CDATA #IMPLIED
open CDATA #IMPLIED
close CDATA #IMPLIED
separator CDATA #IMPLIED
>

<!ELEMENT choose (when* , otherwise?)>
<!ELEMENT when (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
<!ATTLIST when
test CDATA #REQUIRED
>
<!ELEMENT otherwise (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>

<!ELEMENT if (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
<!ATTLIST if
test CDATA #REQUIRED
>
  1. <sql>解析
    解析<sql>元素的逻辑在sqlElement()方法及其重载方法中完成。在解析<sql>过程中,首先判断configuration.getDatabaseId()是否为null,即判断mybatis配置文件中是否配置了<databaseIdProvider>元素。如果配置了<databaseIdProvider>元素,只加载带有databaseId属性,且属性值符合要求的<sql>节点,如果没有配置<databaseIdProvider>元素,就只加载没有带databaseId属性的<sql>元素。

  具体过程如下所示:

  首先sqlElement()方法及其重载方法,完成了解析<sql>元素的逻辑。其中,在sqlElement(List list)方法中,判断了是否配置了<databaseIdProvider>元素,然后调用重载方法sqlElement(List list, String requiredDatabaseId)。在重载方法中,循环处理每个<sql>元素节点,首先解析节点对应的id,databaseId属性,然后根据databaseIdMatchesCurrent()方法判断节点是否符合要求,如果符合要求就添加到了当前实例的sqlFragments变量中,供后续使用,否则继续处理后续的<sql>节点。

//XMLMapperBuilder.java
private void sqlElement(List<XNode> list) throws Exception {
    if (configuration.getDatabaseId() != null) {
      sqlElement(list, configuration.getDatabaseId());
    }
    sqlElement(list, null);
  }

  private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
    for (XNode context : list) {
      String databaseId = context.getStringAttribute("databaseId");
      String id = context.getStringAttribute("id");
      id = builderAssistant.applyCurrentNamespace(id, false);
      if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
        sqlFragments.put(id, context);
      }
    }
  }

  XMLMapperBuilder的databaseIdMatchesCurrent()方法,主要用来判断节点是否符合要求,即当前节点的databaseId属性是否和mybatis配置文件中定义的一致,一致的话返回true,当前节点就会被添加到sqlFragments变量中,否则返回false,不添加到sqlFragments变量中。

疑惑:感觉this.sqlFragments.containsKey(id)一直都会返回false,思考中?

//XMLMapperBuilder.java
private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
    if (requiredDatabaseId != null) {
      if (!requiredDatabaseId.equals(databaseId)) {
        return false;
      }
    } else {
      if (databaseId != null) {
        return false;
      }
      // skip this fragment if there is a previous one with a not null databaseId
      if (this.sqlFragments.containsKey(id)) {
        XNode context = this.sqlFragments.get(id);
        if (context.getStringAttribute("databaseId") != null) {
          return false;
        }
      }
    }
    return true;
  }
六、select、insert、update、delete元素解析
  1. select、insert、update、delete元素节点结构
    通过Mapper文件结构定义文档mybatis-3-mapper.dtd中关于<select>、<insert>、<update>、<delete>元素配置结构,可以知道其相关结构。
<!ELEMENT select (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
<!ATTLIST select
id CDATA #REQUIRED
parameterMap CDATA #IMPLIED
parameterType CDATA #IMPLIED
resultMap CDATA #IMPLIED
resultType CDATA #IMPLIED
resultSetType (FORWARD_ONLY | SCROLL_INSENSITIVE | SCROLL_SENSITIVE | DEFAULT) #IMPLIED
statementType (STATEMENT|PREPARED|CALLABLE) #IMPLIED
fetchSize CDATA #IMPLIED
timeout CDATA #IMPLIED
flushCache (true|false) #IMPLIED
useCache (true|false) #IMPLIED
databaseId CDATA #IMPLIED
lang CDATA #IMPLIED
resultOrdered (true|false) #IMPLIED
resultSets CDATA #IMPLIED 
>

<!ELEMENT insert (#PCDATA | selectKey | include | trim | where | set | foreach | choose | if | bind)*>
<!ATTLIST insert
id CDATA #REQUIRED
parameterMap CDATA #IMPLIED
parameterType CDATA #IMPLIED
timeout CDATA #IMPLIED
flushCache (true|false) #IMPLIED
statementType (STATEMENT|PREPARED|CALLABLE) #IMPLIED
keyProperty CDATA #IMPLIED
useGeneratedKeys (true|false) #IMPLIED
keyColumn CDATA #IMPLIED
databaseId CDATA #IMPLIED
lang CDATA #IMPLIED
>

<!ELEMENT selectKey (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
<!ATTLIST selectKey
resultType CDATA #IMPLIED
statementType (STATEMENT|PREPARED|CALLABLE) #IMPLIED
keyProperty CDATA #IMPLIED
keyColumn CDATA #IMPLIED
order (BEFORE|AFTER) #IMPLIED
databaseId CDATA #IMPLIED
>

<!ELEMENT update (#PCDATA | selectKey | include | trim | where | set | foreach | choose | if | bind)*>
<!ATTLIST update
id CDATA #REQUIRED
parameterMap CDATA #IMPLIED
parameterType CDATA #IMPLIED
timeout CDATA #IMPLIED
flushCache (true|false) #IMPLIED
statementType (STATEMENT|PREPARED|CALLABLE) #IMPLIED
keyProperty CDATA #IMPLIED
useGeneratedKeys (true|false) #IMPLIED
keyColumn CDATA #IMPLIED
databaseId CDATA #IMPLIED
lang CDATA #IMPLIED
>

<!ELEMENT delete (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
<!ATTLIST delete
id CDATA #REQUIRED
parameterMap CDATA #IMPLIED
parameterType CDATA #IMPLIED
timeout CDATA #IMPLIED
flushCache (true|false) #IMPLIED
statementType (STATEMENT|PREPARED|CALLABLE) #IMPLIED
databaseId CDATA #IMPLIED
lang CDATA #IMPLIED
>

<!-- Dynamic -->

<!ELEMENT include (property+)?>
<!ATTLIST include
refid CDATA #REQUIRED
>

<!ELEMENT bind EMPTY>
<!ATTLIST bind
 name CDATA #REQUIRED
 value CDATA #REQUIRED
>
  1. select、insert、update、delete元素节点解析
    和解析<sql>节点类似,首先判断configuration.getDatabaseId()是否为null,即判断mybatis配置文件中是否配置了<databaseIdProvider>元素。然后再调用重载方法buildStatementFromContext(List list, String requiredDatabaseId)进行解析节点,该方法中通过for循环,处理每一个<select>、<insert>、<update>、<delete>元素节点。
//XMLMapperBuilder.java
private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
  }

  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);
      }
    }
  }

在buildStatementFromContext(List list, String requiredDatabaseId)方法中,又通过XMLStatementBuilder类的parseStatementNode()方法解析每个节点。具体方法如下:

public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultType = context.getStringAttribute("resultType");
    String lang = context.getStringAttribute("lang");
    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));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // Parse selectKey after includes and remove them.
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    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;
    }

    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

  在parseStatementNode()方法中,逻辑如下:

  首先,根据databaseId判断是否是符合要求的节点元素,如果符合要求继续执行下面逻辑。
  其次,解析节点上的相关属性,比如:fetchSize、timeout、parameterType、resultMap、resultType、lang、flushCache、useCache、resultOrdered等属性。
  然后,解析节点所属的SqlCommandType类型,即根据节点名称判断是属于下列UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH操作类型中的哪一种。
  再使用XMLIncludeTransformer类的applyIncludes()方法解析,其中包含的子节点<include>,该方法通过递归方式,实现所有下级节点的解析过程。
  然后,再通过processSelectKeyNodes()方法,实现<selectKey>子节点的解析逻辑。解析该节点的时候,也会根据databaseId判断是否是符合要求的节点。
  然后,使用langDriver.createSqlSource()方法创建SqlSource实例对象,SqlSource对象代表了Mapper映射文件或注解中的一个sql语句。
  然后,解析resultSets、keyProperty、keyColumn等属性,然后生成对应的KeyGenerator实例对象,该实例对象用于数据主键的生成。
  最后,使用MapperBuilderAssistant辅助类的addMappedStatement()方法,生成MappedStatement实例对象,并添加到全局唯一变量configuration的mappedStatements变量中,便于后期使用。

七、结尾

  前面已经把Mapper配置文件中除<resultMap >元素之外的其他元素已经分析了,由于<resultMap >元素过于复杂,后续章节单独分析。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

姠惢荇者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值