深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 映射器解析(三)

        我们在第一篇博客中己经对MyBatis基本配置做了详细的解析,细心的读者可能会发现有一个重要的文件映射器没有做详细的分析,今天就来对映射器做一个具体分析吧。在第二篇博客中,我们己经对映射器的使用使用了足够多的例子来说明。映射器解析的关键方法是mapperElement(),下面来做分析吧。
引入映射器的方式有很多种,一般分成以下几种

  1. 用 userMapper.xml引入映射器
<mappers>
    <mapper resource="spring_101_200/config_131_140/spring132_mybatis_typehandlers/UserMapper.xml"></mapper>
</mappers>
  1. 用包名引入映射器
<mappers>
    <package name="com.spring_101_200.test_131_140.test_132_mybatis_typehandlers"/>
</mappers>
  1. 用类注册引入映射器,但是 Mapper和Mapper.xml必需放到一个目录下
<mappers>
    <mapper class="com.spring_101_200.test_131_140.test_132_mybatis_typehandlers.UserMapper"></mapper>
  1. 用文件路径引入映射器
<mappers>
    <mapper url="file:///Users/quyixiao/git/spring_tiny/src/main/resources/spring_101_200/config_131_140/spring132_mybatis_typehandlers/UserMapper.xml"></mapper>
</mappers>
private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
  	//循环遍历mybatis-config.xml文件下的mappers标签下的所有 mapper
    for (XNode child : parent.getChildren()) {
      //如果 mapper节点配置了 package 属性
      if ("package".equals(child.getName())) {
        String mapperPackage = child.getStringAttribute("name");
        configuration.addMappers(mapperPackage);
      } else {
      	//获取 mapper节点下的resource,url,class属性
        String resource = child.getStringAttribute("resource");
        String url = child.getStringAttribute("url");
        String mapperClass = child.getStringAttribute("class");
        if (resource != null && url == null && mapperClass == null) {
          ErrorContext.instance().resource(resource);
          //如果 mapper节点下配置了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);
          //如果 mapper节点下配置了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) {
          //如果 mapper节点下配置了class属性
          Class<?> mapperInterface = Resources.classForName(mapperClass);
          configuration.addMapper(mapperInterface);
        } else {
          throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
        }
      }
    }
  }
}

        配置了 class 属性需要Mapper.xml 和Mapper.java 放到同一个目录下,而url 属性需要指定绝对路径,不方便迁移,因此,我们大部分时候用的是配置了resource 属性的 mapper标签。因此,今天着重使用配置了 resource属性的 mapper 标签解析。

public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
  	//对 mapper标签下的所有节点进行解析
    configurationElement(parser.evalNode("/mapper"));
    configuration.addLoadedResource(resource);
    bindMapperForNamespace();
  }
	
  //当extends其他Mapper.xml中的resultMap时,而其他的Mapper.xml没有被加载
  parsePendingResultMaps();
  //当cache-ref引用的Mapper.xml没有被加载时,调用此方法补全
  parsePendingChacheRefs();
  parsePendingStatements();
}

        configurationElement()方法基本上将Mapper.xml 文件中所有的元素做了解析,并注册到 Configuration中去。

private void configurationElement(XNode context) {
 try {
 	//获取命名空间
    String namespace = context.getStringAttribute("namespace");
    if (namespace.equals("")) {
  	  throw new BuilderException("Mapper's namespace cannot be empty");
    }
    //设置命名空间
    builderAssistant.setCurrentNamespace(namespace);
    //解析cache-ref节点
    cacheRefElement(context.evalNode("cache-ref"));
    //解析cache节点
    cacheElement(context.evalNode("cache"));
    //解析parameterMap节点
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    //解析resultMap节点
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    //解析sql节点
    sqlElement(context.evalNodes("/mapper/sql"));
    //解析select,insert,update,delete节点,详情请看其使用
    buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
  }
}

        在解析标签之前,我们先对cache,cache-ref 先做一个了解。

这句简单的语句作用如下:

  • 所有映射文件里的 select 语句的结果都会被缓存。

  • 所有映射文件里的 insert、update 和 delete 语句执行都会清空缓存。

  • 缓存使用最近最少使用算法(LRU)来回收。

  • 缓存不会被设定的时间所清空。

  • 每个缓存可以存储 1024 个列表或对象的引用(不管查询方法返回的是什么)。

  • 缓存将作为“读/写”缓存,意味着检索的对象不是共享的且可以被调用者安全地修改,而不会被其它调用者或者线程干扰。

所有这些特性都可以通过 cache 元素进行修改。例如:
<cache
        eviction=“FIFO”
        flushInterval=“60000”
        size=“512”
        readOnly=“true”/>
这种高级的配置创建一个每 60 秒刷新一次的 FIFO 缓存,存储 512 个结果对象或列表的引用,并且返回的对象是只读的。因此在不用的线程里的调用者修改它们可能会引用冲突。

可用的回收算法如下:

  • LRU:最近最少使用:移出最近最长时间内都没有被使用的对象。

  • FIFO:先进先出:移除最先进入缓存的对象。

  • SOFT:软引用:基于垃圾回收机制和软引用规则来移除对象(空间内存不足时才进行回收)。

  • WEAK:弱引用:基于垃圾回收机制和弱引用规则(垃圾回收器扫描到时即进行回收)。

默认使用LRU

  • flushInterval:设置任何正整数,代表一个以毫秒为单位的合理时间。默认是没有设置,因此没有刷新间隔时间被使用,在语句每次调用时才进行刷新。

  • Size:属性可以设置为一个正整数,您需要留意您要缓存对象的大小和环境中可用的内存空间。默认是1024。

  • readOnly:属性可以被设置为 true 或 false。只读缓存将对所有调用者返回同一个实例。因此这些对象都不能被修改,这可以极大的提高性能。可写的缓存将通过序列化来返回一个缓存对象的拷贝。这会比较慢,但是比较安全。所以默认值是 false。

        在解析cache,cache-ref之前,我们先也了解这两个属性的使用,我们都知道,系统缓存是 MyBatis 应用机器上本地缓存,但是在大型服务器上,会使用各类不同的缓存服务器,这个时候我们可以定制缓存,比如现在流行的 Redis 缓存。为了方便测试,我们自定义缓存,自定义缓存需要实现 MyBatis 为我们提供的 org.apache.ibatis.cache.Cache,缓存接口如下,

//获取缓存编号
getId();
//保存 Key值缓存对象
pubObject(Object key,Object value);
//通过 key 获取缓存对象
Object getObject(Object key);
//通过 key 删除缓存对象
Object removeObject(Object key);
//清空缓存
void clear();
//获取缓存大小
int getSize();
//获取缓存的读写锁
ReadWriteLock getReadWriteLock();

  1. 创建 POJO
@Data
public class User implements Serializable {
    private Long id;
    private int isDelete;
    private Date gmtCreate;
    private Date gmtModified;
    private String username;
    private String password;
    private String realName;
    private Long managerId;

}
  1. 创建 Mapper
public interface UserMapper {
    User getUser(@Param("id") Long id);
}

public interface UserBillMapper {
    User getUser(@Param("id") Long id);
}
  1. 创建 Mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.spring_101_200.test_141_150.test_145_mybatis_cachenamespaceref_xml.UserMapper" >

    <cache type="com.spring_101_200.test_141_150.test_145_mybatis_cachenamespaceref_xml.MybatisPlusCache">
        <!--eviction(收回策略)
            LRU(最近最少使用的):移除最长时间不被使用的对象,这是默认值。
            FIFO(先进先出):按对象进入缓存的顺序来移除它们。
            SOFT(软引用):移除基于垃圾回收器状态和软引用规则的对象。
            WEAK(弱引用):更积极地移除基于垃圾收集器状态和弱引用规则的对象。
        -->
        <property name="eviction" value="LRU" />
        
        <!--flushinterval(刷新间隔)
            可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况不设置,即没有刷新间隔,缓存仅仅在调用语句时刷新。-->
        <property name="flushInterval" value="6000000" />
        <!--size(引用数目)
            可以被设置为任意正整数,要记住缓存的对象数目和运行环境的可用内存资源数目。默认值是1024 。-->
        <property name="size" value="1024" />
        
        <!--readOnly(只读)
            属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改,这提供了很重要的性能优势。
            可读写的缓存会通过序列化返回缓存对象的拷贝,这种方式会慢一些,但是安全,因此默认是 false。-->
        <property name="readOnly" value="false" />
    </cache>

    <select id="getUser" parameterType="java.lang.Long" resultType="User">
            select * from lz_user where id=#{id}
    </select>
    
</mapper>


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.spring_101_200.test_141_150.test_145_mybatis_cachenamespaceref_xml.UserBillMapper" >

    <cache-ref namespace="com.spring_101_200.test_141_150.test_145_mybatis_cachenamespaceref_xml.UserMapper"/>
    
    <select id="getUser" parameterType="java.lang.Long" resultType="User">
            select * from lz_user where id=#{id}
    </select>
</mapper>
  1. 创建自定义缓存类MybatisPlusCache
public class MybatisPlusCache implements Cache {
    // 读写锁
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);

    //这里使用了redis缓存,使用springboot自动注入
    private HashMap<String, Object> cacheMap = new HashMap<>();

    private String id;

    
    //是mybatis必须要求的,必写。此id是xml中的namespace的值
    public MybatisPlusCache(final String id) {
        if (id == null) {
            throw new IllegalArgumentException("未获取到缓存实例id");
        }
        this.id = id;
    }
    
    //返回cache的唯一名称
    @Override
    public String getId() {
        return this.id;
    }
    
    //缓存存值
    @Override
    public void putObject(Object key, Object value) {
        String k = id + "_" + key.toString();
        //id是namespace的值,key是方法名,value是查询的结果
        System.out.println("putObject id:           " + id);
        System.out.println("putObject key:           " + key);
        String myK  = getKey(key);
        if(StringUtils.isNotBlank(myK)){
            System.out.println("putObject myK:      " + myK     );
            k = myK;
        }
        cacheMap.put(k, value);
    }

    //缓存取值
    @Override
    public Object getObject(Object key) {
        String k = id + "_" + key.toString();
        System.out.println("getObject id:           " + id);
        System.out.println("getObject key:           " + key);
        String myK  = getKey(key);
        if(StringUtils.isNotBlank(myK)){
            System.out.println("getObject myK:      " + myK     );
            k = myK;
        }
        Object obj =  cacheMap.get(k);
        return obj;
    }
    public String getKey(Object key){
        if(key instanceof CacheKey){
            String keys [] = key.toString().split(":");
            return keys[keys.length-2] + keys[keys.length-1];
        }
        return null;
    }

    //mybatis保留方法
    @Override
    public Object removeObject(Object key) {
        return null;
    }

    @Override
    public void clear() {
        cacheMap.clear();
    }

    @Override
    public int getSize() {
        int i = 0;
        for (String key : cacheMap.keySet()) {
            i++;
        }
        return i;
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return this.readWriteLock;
    }

    public boolean equals(Object o) {
        if (getId() == null) throw new CacheException("Cache instances require an ID.");
        if (this == o) return true;
        if (!(o instanceof Cache)) return false;
        
        Cache otherCache = (Cache) o;
        return getId().equals(otherCache.getId());
    }
    
    public int hashCode() {
        if (getId() == null) throw new CacheException("Cache instances require an ID.");
        return getId().hashCode();
    }
}

        为了测试 cache-ref 标签,不同的 Mapper 使用相同的缓存,在这里做了一点点小的变动,将 get 和 put 的 key 变成了 sql + sql 参数的形式,也就是不同的 Mapper 中,只要查询的 SQL一样,将使用相同的缓存 。

  1. 测试
public void test1() throws Exception {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    UserBillMapper userBillMapper = sqlSession.getMapper(UserBillMapper.class);
    System.out.println("首次通过userMapper查询用户信息:");
    User user = userMapper.getUser(456l);
    System.out.println("user:" + JSON.toJSONString(user));
    sqlSession.commit();
    System.out.println("第二次通过userMapper查询用户信息:");
    user = userMapper.getUser(456l);
    System.out.println("第三次通过userBillMapper查询用户信息:");
    User user2 = userBillMapper.getUser(456l);
    System.out.println("user2:"+JSON.toJSONString(user2));
}

结果:
在这里插入图片描述
        从结果中我们可以看出,不同的Mapper使用了相同的缓存。

private void cacheRefElement(XNode context) {
  if (context != null) {
  	//将currentNamespace和refNamespace存储于configuration中的cacheRefMap中
    configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
    //创建缓存引用解析器,将builderAssistant和引用命名空间存储于CacheRefResolver属性中
    CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
    try {
    	//解析缓存
        cacheRefResolver.resolveCacheRef();
    } catch (IncompleteElementException e) {
        configuration.addIncompleteCacheRef(cacheRefResolver);
    }
  }
}
public class CacheRefResolver {
	private final MapperBuilderAssistant assistant;
	private final String cacheRefNamespace;
	public CacheRefResolver(MapperBuilderAssistant assistant, String cacheRefNamespace) {
		this.assistant = assistant;
		this.cacheRefNamespace = cacheRefNamespace;
	}
	public Cache resolveCacheRef() {
		//调用MapperBuilderAssistant的useCacheRef方法
		return assistant.useCacheRef(cacheRefNamespace);
	}
}
public Cache useCacheRef(String namespace) {
  //如果cache-ref没有配置命名空间,抛出异常
  if (namespace == null) {
    throw new BuilderException("cache-ref element requires a namespace attribute.");
  }
  try {
    unresolvedCacheRef = true;
    //从configuration中caches获取引用命名空间的Cache
    Cache cache = configuration.getCache(namespace);
    if (cache == null) {
      throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
    }
    //设置当前命名空间的cache和cache-ref命名空间为为相同的cache
    currentCache = cache;
    unresolvedCacheRef = false;
    return cache;
  } catch (IllegalArgumentException e) {
    throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
  }
}

        接下来,我们继续分析cache标签的解析。

private void cacheElement(XNode context) throws Exception {
  if (context != null) {
  	//获取cache的type属性,默认为PERPETUAL
    String type = context.getStringAttribute("type", "PERPETUAL");
    //获取type属性所对应的Java类
    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");
    //属性可以设置为一个正整数,您需要留意您要缓存对象的大小和环境中可用的内存空间。默认是1024。
    Integer size = context.getIntAttribute("size");
    //属性可以被设置为 true 或 false。只读缓存将对所有调用者返回同一个实例。因此这些对象都不能被修改,这可以极大的提高性能。
    //可写的缓存将通过序列化来返回一个缓存对象的拷贝。这会比较慢,但是比较安全。所以默认值是 false。
    boolean readWrite = !context.getBooleanAttribute("readOnly", false);
    Properties props = context.getChildrenAsProperties();
    //创建缓存对象
    builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, props);
  }
}
public Cache useNewCache(Class<? extends Cache> typeClass,
    Class<? extends Cache> evictionClass,
    Long flushInterval,
    Integer size,
    boolean readWrite,
    Properties props) {
  //如果缓存类为空,设置默认类为PerpetualCache
  typeClass = valueOrDefault(typeClass, PerpetualCache.class);
  //如果删除算法为空,设置默认为LruCache类
  evictionClass = valueOrDefault(evictionClass, LruCache.class);
  Cache cache = new CacheBuilder(currentNamespace)
      .implementation(typeClass)
      .addDecorator(evictionClass)
      .clearInterval(flushInterval)
      .size(size)
      .readWrite(readWrite)
      .properties(props)
      .build();
  //保存cache到configuration的caches的Map中,key为当前命名空间
  configuration.addCache(cache);
  currentCache = cache;
  return cache;
}

parameterMap标签解析
        在解析parameterMap之前,我们来看看parameterMap的使用。

<parameterMap id="parameterMap" type="User">
    <parameter property="id"></parameter>
    <parameter property="password"></parameter>
    <parameter property="realName"></parameter>
</parameterMap>


<select id="getUserByParameterMap" parameterMap="parameterMap" resultType="User">
    select * from lz_user where id=#{id} or password=#{password} or real_name = #{realName}
</select>

下面,我们来解析parameterMap吧

private void parameterMapElement(List<XNode> list) throws Exception {
    for (XNode parameterMapNode : list) {
    	//获取parameterMap的type和id属性
        String id = parameterMapNode.getStringAttribute("id");
        String type = parameterMapNode.getStringAttribute("type");
        Class<?> parameterClass = resolveClass(type);
        //获取parameter标签的所有子节点
        List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter");
        List<ParameterMapping> parameterMappings = new ArrayList <ParameterMapping>();
        //对所有的parameter节点遍历
        for (XNode parameterNode : parameterNodes) {
        	//获取property,javaType,jdbcType,resultMap,mode,typeHandler,numericScale等属性
            String property = parameterNode.getStringAttribute("property");
            String javaType = parameterNode.getStringAttribute("javaType");
            String jdbcType = parameterNode.getStringAttribute("jdbcType");
            String resultMap = parameterNode.getStringAttribute("resultMap");
            String mode = parameterNode.getStringAttribute("mode");
            String typeHandler = parameterNode.getStringAttribute("typeHandler");
            Integer numericScale = parameterNode.getIntAttribute("numericScale");
            ParameterMode modeEnum = resolveParameterMode(mode);
            Class<?> javaTypeClass = resolveClass(javaType);
            JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
            @SuppressWarnings("unchecked")
                    Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
            //构建ParameterMapping
            ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property, javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
            parameterMappings.add(parameterMapping);
        }
        //将ParameterMapping加入到configuration的parameterMaps中,key为当前Mapper的namespace
        builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
    }
}

resultMap标签解析
在解析之前,先来看看resultMap标签结构

<resultMap>
    <constructor>
        <idArg/>
        <arg/>
    </constructor>
    <id />
    <result />
    <association />
    <collection />
    <discriminator >
        <case />
    </discriminator>
</resultMap>

        其中 constructor 元素用于配置构造方法,一个 POJO 可能不存在没有参数的构造方法。这个时候,我们就可以使用 constructor 进行配置,假设角色 UserBean 不存在没有参数的构造方法,它的构造方法声明为 public User(int id ,String username),那么我们需要配置这个结果集,虽然大家对resultMap的使用己经足够多了,这里还是列举一下实际情况。

 <resultMap type="com.spring_101_200.test_141_150.test_146_mybatis_discriminator.UserBill" id="ordersUserLazyLoading">
    <id column="id" property="id"/>
    <result column="is_delete" property="isDelete"/>
    <result column="user_id" property="userId"/>
    <result column="amount" property="amount"/>
    <result column="type" property="type"/>
    <discriminator javaType="int" column="is_delete">
        <case value="1" resultType="com.spring_101_200.test_141_150.test_146_mybatis_discriminator.UserBill">
            <association property="user" javaType="User" select="findUserById" column="user_id"/>
        </case>
    </discriminator>
</resultMap>

开始解析

XMLMapperBuilder.java
private void resultMapElements(List<XNode> list) throws Exception {
	//遍历所有resultMap标签
    for (XNode resultMapNode : list) {
        try {
            resultMapElement(resultMapNode);
        } catch (IncompleteElementException e) {
        }
    }
}

private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
	//对每个resultMap标签解析
    return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());
}

private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
	//获取resultMap标签中的id属性
    String id = resultMapNode.getStringAttribute("id",resultMapNode.getValueBasedIdentifier());
    //获取resultMap中的type,ofType,resultType,javaType中任意一个属性,也就是说resultMap标签中只能配置type,ofType,resultType,javaType四个属性中的其中一个
    String type = resultMapNode.getStringAttribute("type",
            resultMapNode.getStringAttribute("ofType",
                    resultMapNode.getStringAttribute("resultType",
                            resultMapNode.getStringAttribute("javaType"))));
    //获取resultMap标签中的extends属性
    String extend = resultMapNode.getStringAttribute("extends");
    //获取resultMap标签中autoMapping属性
    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();
    //对resultMap标签下的所有节点遍历
    for (XNode resultChild : resultChildren) {
        if ("constructor".equals(resultChild.getName())) {
            processConstructorElement(resultChild, typeClass, resultMappings);
        } else if ("discriminator".equals(resultChild.getName())) {
        	//对discriminator标签
            discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
        } else {
            ArrayList<ResultFlag> flags = new ArrayList<ResultFlag>();
            //对id标签解析
            if ("id".equals(resultChild.getName())) {
                flags.add(ResultFlag.ID);
            }
            //对result标签标签解析
            resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
        }
    }
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
    	//将ResultMapping加入到configuration中的resultMaps中
        return resultMapResolver.resolve();
    } catch (IncompleteElementException e) {
        configuration.addIncompleteResultMap(resultMapResolver);
        throw e;
    }
}
public ResultMap resolve() {
    return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
}
public ResultMap addResultMap(
        String id,
        Class<?> type,
        String extend,
        Discriminator discriminator,
        List<ResultMapping> resultMappings,
        Boolean autoMapping) {
    id = applyCurrentNamespace(id, false);
    extend = applyCurrentNamespace(extend, true);
    ResultMap.Builder resultMapBuilder = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping);
    if (extend != null) {
    	//是否有继承
        if (!configuration.hasResultMap(extend)) {
        	//如果继承的Map在configuration中没有,抛出IncompleteElementException异常
            throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
        }
        ResultMap resultMap = configuration.getResultMap(extend);
        List<ResultMapping> extendedResultMappings = new ArrayList<ResultMapping>(resultMap.getResultMappings());
        //当前resultMap中的ResultMapping覆盖继承过来的ResultMapping
        extendedResultMappings.removeAll(resultMappings);
        boolean declaresConstructor = false;
        for (ResultMapping resultMapping : resultMappings) {
        	//当前ResultMap中是否配置了constructor
            if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
                declaresConstructor = true;
                break;
            }
        }
        //如果当前ResultMap中配置了constructor
        if (declaresConstructor) {
        	//遍历继承过来的ResultMap
            Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
            while (extendedResultMappingsIter.hasNext()) {
                if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
                	//如果继承过来的ResultMap中也有constructor标签,则我移除掉
                    extendedResultMappingsIter.remove();
                }
            }
        }
        resultMappings.addAll(extendedResultMappings);
    }
    //设置监别器
    resultMapBuilder.discriminator(discriminator);
    ResultMap resultMap = resultMapBuilder.build();
    configuration.addResultMap(resultMap);
    return resultMap;
}

        这一段代码主要是对继承过来的resultMap做处理,如果子ResultMap中ResultMapping覆盖父ResultMap中的ResultMapping,子ResultMap中的constructor标签覆盖父constructor标签。

private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
    List<XNode> argChildren = resultChild.getChildren();
	//对constructor节点遍历
	<constructor>
    	<arg></arg>
    	<idArg></idArg>
	</constructor>
    for (XNode argChild : argChildren) {
        ArrayList<ResultFlag> flags = new ArrayList<ResultFlag>();
        flags.add(ResultFlag.CONSTRUCTOR);
        if ("idArg".equals(argChild.getName())) {
        	//如果constructor标签下有idArg标签
            flags.add(ResultFlag.ID);
        }
        resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
    }
}
private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
	//获取discriminator标签的column,javaType,jdbcType,typeHandler属性
    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>();
    //获取discriminator标签下的value,resultMap或resultType属性
    for (XNode caseChild : context.getChildren()) {
        String value = caseChild.getStringAttribute("value");
        String resultMap = caseChild.getStringAttribute("resultMap", processNestedResultMappings(caseChild, resultMappings));
        discriminatorMap.put(value, resultMap);
    }
    //构建Discriminator对象
    return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap);
}
private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings) throws Exception {
	//如果是association,collection,case标签下的一个
    if ("association".equals(context.getName())
            || "collection".equals(context.getName())
            || "case".equals(context.getName())) {
        //如果标签下的没有select属性
        if (context.getStringAttribute("select") == null) {
        	//再次对resultMap属性进行解析
            ResultMap resultMap = resultMapElement(context, resultMappings);
            return resultMap.getId();
        }
    }
    return null;
}
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, ArrayList<ResultFlag> flags) throws Exception {
	//获取property,column,javaType,jdbcType属性
    String property = context.getStringAttribute("property");
    String column = context.getStringAttribute("column");
    String javaType = context.getStringAttribute("javaType");
    String jdbcType = context.getStringAttribute("jdbcType");
    //对于<association property="user" javaType="User" select="findUserById" column="user_id"/>这种情况的考虑
    String nestedSelect = context.getStringAttribute("select");
    String nestedResultMap = context.getStringAttribute("resultMap",
    		//如果没有resultMap则对resultType解析
            processNestedResultMappings(context, Collections.<ResultMapping> emptyList()));
    //对notNullColumn,columnPrefix,typeHandler,resultSet,foreignColumn,fetchType属性获取
    String notNullColumn = context.getStringAttribute("notNullColumn");
    String columnPrefix = context.getStringAttribute("columnPrefix");
    //<result column="sex" property="sex" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>
    //<result column="sex_str" property="sexStr" typeHandler="org.apache.ibatis.type.EnumTypeHandler"/>
    //上述就是resultMap中配置typeHandler配置使用,下面就是对result标签中typeHandler属性解析
    String typeHandler = context.getStringAttribute("typeHandler");
    //对result标签中resultSet,foreignColumn属性解析
    String resulSet = context.getStringAttribute("resultSet");
    String foreignColumn = context.getStringAttribute("foreignColumn");
    //对于特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态
    boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
    Class<?> javaTypeClass = resolveClass(javaType);
    @SuppressWarnings("unchecked")
    Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    //构建ResultMapping返回
    return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resulSet, foreignColumn, lazy);
}

sql标签解析

private void sqlElement(List<XNode> list) throws Exception {
	//<databaseIdProvider type="DB_VENDOR">
    //		<property name="MySQL" value="mysql" />
    //		<property name="Oracle" value="oracle" />
	//</databaseIdProvider>
	//如果在全局设置mybatis-config.xml配置了databaseIdProvider,如果当前数据库是mysql,则DatabaseId的值为mysql
    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");
        //<sql id="Base_Column_List">
    	//	id AS id, is_delete AS isDelete, gmt_create AS gmtCreate
		//</sql>
		//获取sql标签中的id属性
        String id = context.getStringAttribute("id");
        id = builderAssistant.applyCurrentNamespace(id, false);
        //将当前节点保存到sqlFragments中
        if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) sqlFragments.put(id, context);
    }
}

select|insert|update|delete标签解析
        对select,insert,update,delete标签解析,在解析之前,还是看一下如何使用吧。

UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sina.mapper.user.UserMapper">
    <resultMap id="BaseResultMap" type="com.sina.model.entity.user.User">
        <id column="id" property="id"/>
        <result column="is_delete" property="isDelete"/>
        <result column="gmt_create" property="gmtCreate"/>
        <result column="gmt_modified" property="gmtModified"/>
        <result column="username" property="username"/>
        <result column="password" property="password"/>
        <result column="real_name" property="realName"/>
        <result column="manager_id" property="managerId"/>
        <result column="sex" property="sex"/>
        <result column="sex_str" property="sexStr"/>
    </resultMap>

    <sql id="Base_Column_List">
        id AS id, is_delete AS isDelete, gmt_create AS gmtCreate, gmt_modified AS gmtModified, username AS username, password AS password, real_name AS realName, manager_id AS managerId, sex AS sex, sex_str AS sexStr
    </sql>

    <select id="selectUserById" resultType="User" >
        select * from lz_user  where id=#{id} and is_delete = 0 limit 1 
    </select>

    <insert id="insertUser" parameterType="User" useGeneratedKeys="true" keyProperty="id" >
        insert into lz_user(
            <if test="username != null">username, </if>
            <if test="password != null">password, </if>
            <if test="realName != null">real_name, </if>
            <if test="managerId != null">manager_id, </if>
            <if test="sex != null">sex, </if>
            <if test="sexStr != null">sex_str, </if>
            is_delete,
            gmt_create,
            gmt_modified
        )values(
            <if test="username != null">#{ username}, </if>
            <if test="password != null">#{ password}, </if>
            <if test="realName != null">#{ realName}, </if>
            <if test="managerId != null">#{ managerId}, </if>
            <if test="sex != null">#{ sex}, </if>
            <if test="sexStr != null">#{ sexStr}, </if>
            0,
            now(),
            now()
        )
    </insert>

    <update id="updateUserById" parameterType="User" >
        update
            lz_user
        <trim prefix="set" suffixOverrides=",">
            <if test="isDelete != null">is_delete = #{isDelete},</if>
            <if test="gmtCreate != null">gmt_create = #{gmtCreate},</if>
            <if test="username != null">username = #{username},</if>
            <if test="password != null">password = #{password},</if>
            <if test="realName != null">real_name = #{realName},</if>
            <if test="managerId != null">manager_id = #{managerId},</if>
            <if test="sex != null">sex = #{sex},</if>
            <if test="sexStr != null">sex_str = #{sexStr}</if>                
        </trim>
        ,gmt_modified = now()
        where id = #{id}
    </update>
    
    <delete id="deleteUserById" parameterType="java.lang.Long">
        delete from lz_user where id=#{id} limit 1  
    </delete>
</mapper>

        上述是一个正常的MyBatis POJO增删改查。

XMLMapperBuilder.java
private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
        buildStatementFromContext(list, configuration.getDatabaseId());
    }
    //构建Statement
    buildStatementFromContext(list, null);
}

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
	//对mapper标签下的所有select,update,delete,insert标签遍历
    for (XNode context : list) {
        final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
        try {
        	//对select,update,delete,insert标签之一进行解析
            statementParser.parseStatementNode();
        } catch (IncompleteElementException e) {
            configuration.addIncompleteStatement(statementParser);
        }
    }
}
XMLStatementBuilder.java
public void parseStatementNode() {
	//获取节点id
    String id = context.getStringAttribute("id");
    //<databaseIdProvider type="DB_VENDOR">
    //		<property name="MySQL" value="mysql" />
    //		<property name="Oracle" value="oracle" />
	//</databaseIdProvider>
	//如果配置了databaseIdProvider,并且当前数据库环境是MySQL,将对配置了oracle属性的标签不做解析
    String databaseId = context.getStringAttribute("databaseId");
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) return;
    //获取fetchSize属性
    Integer fetchSize = context.getIntAttribute("fetchSize");
    //获取标签中的timeout属性,为sql执行超时时间
    Integer timeout = context.getIntAttribute("timeout");
    //获取标签参数map属性
    String parameterMap = context.getStringAttribute("parameterMap");
    //获取标签参数类型属性
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    //获取标签resultMap属性
    String resultMap = context.getStringAttribute("resultMap");
    //获取标签resultType属性
    String resultType = context.getStringAttribute("resultType");
    //这个属性用得少,默认为XMLLanguageDriver.class
    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();
    //获取当前SQL类型,select or update or insert or delete
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    //当前sql是否是select
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    //非select标签,flushCache属性为true
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    //select标签,useCache属性为true,使用本地缓存
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    //mybatis的官方文档select元素的属性有一个属性resultOrdered,这个设置仅针对嵌套结果 select 语句:
    //如果为 true,将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。 
    //这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false
    //在com.spring_101_200.test_141_150.test_149_mybatis_resultordered包中,我测试一下,好像没有看到什么效果
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
	
	//<select id="selectSqlUserById" resultType="User" >
    //	select <include refid="Base_Column_List"/> from lz_user  where id=#{id} and is_delete = 0 limit 1
 	//</select> 
 	//在标签中使用include标签
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

  	//解析SelectKey标签
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
	//将select * from user where id = #{id}转化为select * from user where id =? 
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    
    String resultSets = context.getStringAttribute("resultSets");
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    KeyGenerator keyGenerator;
    //构建 id = 命名空间+标签id+!SelectKey
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    //如果标签中配置了<SelectKey /> 标签
    if (configuration.hasKeyGenerator(keyStatementId)) {
        keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
    	//如果没有配置<SelectKey />标签,看当前标签中是否配置了useGeneratedKeys属性
        keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
                configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
                ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
    }
	//当前标签构建Statement存储于configuration中
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
            fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
            resultSetTypeEnum, flushCache, useCache, resultOrdered,
            keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
XMLIncludeTransformer.java
public void applyIncludes(Node source) {
    if (source.getNodeName().equals("include")) {
    	//根据include标签从sqlFragments获取refid对应的sql标签
        Node toInclude = findSqlFragment(getStringAttribute(source, "refid"));
       	//<sql id="Base_Base_Column_List">
		//     username AS username, password AS password, real_name AS realName, manager_id AS managerId, sex AS sex, sex_str AS sexStr
		//</sql>
		//<sql id="Base_Column_List">
		//    id AS id, is_delete AS isDelete, gmt_create AS gmtCreate, gmt_modified AS gmtModified, <include refid="Base_Base_Column_List"/>
		//</sql>
		//这里需要考虑的一种情况是在<sql/>标签中有<include/>标签,因此会递归调用applyIncludes()方法
        applyIncludes(toInclude);
        if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {
			//<select id="selectSqlUserById" resultMap="BaseResultMap" >
    		//	select <include refid="com.spring_101_200.test_141_150.test_148_mybatis_selectprovider.UserBillMapper.Base_Column_List"/> 
    		//  from lz_user where id=#{id} and is_delete = 0 limit 1
			//</select>
			//从其他Mapper.xml中引入<sql/>标签
            toInclude = source.getOwnerDocument().importNode(toInclude, true);
        }
        //将<include/>标签替换成<sql/>标签
        source.getParentNode().replaceChild(toInclude, source);
        while (toInclude.hasChildNodes()) {
        	//用<sql/>标签下的 id AS id, is_delete AS isDelete, gmt_create AS gmtCreate ... 插入到<sql/>标签前面
            toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);
        }
        //删除<sql/>标签
        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));
        }
    }
}

        在include标签的解析过程中考虑到很多情况的,先判断<sql/>标签中是否有<include/>标签,如果有,递归调用applyIncludes(),再判断include标签和sql标签是不是同一个命名空间,如果不是,则导入节点,再将<include/>标签替换成<sql/>标签,之后将<sql/>的内容提到<sql/>标签前面,再删除<sql/>标签,这个方法中有3个不起眼的方法replaceChild(),insertBefore(),removeChild(),下面来对这三个方法进行分析。

ParentNode.java
public Node replaceChild(Node newChild, Node oldChild)
    throws DOMException {
    
    //通知文档
    ownerDocument.replacingNode(this);
	//将<sql/>节点插入到<include/>标签前
    internalInsertBefore(newChild, oldChild, true);
    if (newChild != oldChild) {
    	//移除掉<include/>标签
        internalRemoveChild(oldChild, true);
    }
	//通知文档对象
    ownerDocument.replacedNode(this);
    return oldChild;
}

        对于替换节点,主要是将新节点插入到旧节点前面,然后将旧节点删除,就完成了节点替换。先声明一下,这里只对

<sql id="Base_Column_List">
    select 
</sql>

<select id="selectSqlUserById" resultType="User"  ><include refid="Base_Column_List"/> * from lz_user  where id=#{id} and is_delete = 0 limit 1
</select>

<sql id="Base_Column_List">
    *
</sql>

<select id="selectSqlUserById" resultType="User"  >
    select <include refid="Base_Column_List"/> from lz_user  where id=#{id} and is_delete = 0 limit 1
</select>

        两种情况测试说明,因此newChild一般认为是<sql/>节点,而refChild节点认为是<include/>节点,而对于下面这种情况<sql/>标签中有<sql/>的,用户自行去测试一下吧

<sql id="a">
    id,username,
</sql>

<sql id="b">
   <include refid="a"/> real_name as realName
</sql>
ParentNode.java
public Node insertBefore(Node newChild, Node refChild)    throws DOMException {
    return internalInsertBefore(newChild, refChild, false);
}

        对于插入节点而言,就是在newChild节点前插入refChild节点

ParentNode.java
Node internalInsertBefore(Node newChild, Node refChild, boolean replace)
    throws DOMException {
    boolean errorChecking = ownerDocument.errorChecking;
	//如果节点是DocumentFragment
    if (newChild.getNodeType() == Node.DOCUMENT_FRAGMENT_NODE) {
        if (errorChecking) {
            for (Node kid = newChild.getFirstChild(); 
                 kid != null; kid = kid.getNextSibling()) {
                if (!ownerDocument.isKidOK(this, kid)) {
                    throw new DOMException(
                          DOMException.HIERARCHY_REQUEST_ERR,
                          DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "HIERARCHY_REQUEST_ERR", null));
                }
            }
        }
        while (newChild.hasChildNodes()) {
            insertBefore(newChild.getFirstChild(), refChild);
        }
        return newChild;
    }
    
    if (newChild == refChild) {
        refChild = refChild.getNextSibling();
        removeChild(newChild);
        insertBefore(newChild, refChild);
        return newChild;
    }

    if (needsSyncChildren()) {
        synchronizeChildren();
    }
	//错误检查
    if (errorChecking) {
        if (isReadOnly()) {
            throw new DOMException(
                          DOMException.NO_MODIFICATION_ALLOWED_ERR,
                          DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null));
        }
        if (newChild.getOwnerDocument() != ownerDocument && newChild != ownerDocument) {
            throw new DOMException(DOMException.WRONG_DOCUMENT_ERR,
                        DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null));
        }
        if (!ownerDocument.isKidOK(this, newChild)) {
            throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
                        DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "HIERARCHY_REQUEST_ERR", null));
        }
        //refChild必须是此节点的子级(或为null)
        if (refChild != null && refChild.getParentNode() != this) {
            throw new DOMException(DOMException.NOT_FOUND_ERR,
                        DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null));
        }
        
        //防止树中的循环,newChild不能是该Node的祖先,实际上不能是
        if (ownerDocument.ancestorChecking) {
            boolean treeSafe = true;
            for (NodeImpl a = this; treeSafe && a != null; a = a.parentNode())
            {
                treeSafe = newChild != a;
            }
            if(!treeSafe) {
                throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
                            DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "HIERARCHY_REQUEST_ERR", null));
            }
        }
    }
	//插入节点前通知
    ownerDocument.insertingNode(this, replace);
    
    ChildNode newInternal = (ChildNode)newChild;

    Node oldparent = newInternal.parentNode();
    if (oldparent != null) {
    	//<sql id="a">
		//    id
		//</sql>
		//<sql id="Base_Column_List">
		//    <include refid="a"/>,username
		//</sql>
		//如果是以上配置,将<sql id="a" />下的id节点从<sql id="a" />中移除
        oldparent.removeChild(newInternal);
    }

    ChildNode refInternal = (ChildNode)refChild;
	//新节点的属主为当前节点,比如设置<sql/>标签的属主为<select/>节点
    newInternal.ownerNode = this;
    newInternal.isOwned(true);
 
    if (firstChild == null) {
    	//如果循环链表中没有元素,newInternal为第一个元素
        firstChild = newInternal;
        newInternal.isFirstChild(true);
        newInternal.previousSibling = newInternal;
    }
    else {
    	//如果插入节点在循环链表最后
        if (refInternal == null) {
        	//firstChild的prev为最后一个节点
            ChildNode lastChild = firstChild.previousSibling;
            //lastChild的next为newInternal
            lastChild.nextSibling = newInternal;
            //newInternal的prev为最后一个节点
            newInternal.previousSibling = lastChild;
            //firstChild的prev指向新节点newInternal
            firstChild.previousSibling = newInternal;
        }
        else {
            if (refChild == firstChild) {
                //在前面插入
                //<select id="selectSqlUserById" resultType="User"><include refid="Base_Column_List"/>select * from lz_user  where id=#{id} and is_delete = 0 limit 1
    			//</select>
                //如果<include/>标签紧接在<select/>之后,连空格都没有,将会走下面代码,其实就是在循环链表头插入一个节点<sql/>而已
                //firstChild(<include/>)设置不是<select/>节点下的第一个节点
                firstChild.isFirstChild(false);
                //<sql/>的next节点是<include/>
                newInternal.nextSibling = firstChild;
                //<sql/>节点prev设置为<include/>的prev
                newInternal.previousSibling = firstChild.previousSibling;
                //<include/>节点的prev节点是<sql/>
                firstChild.previousSibling = newInternal;
                //<select/>下第一个节点是<sql/>节点
                firstChild = newInternal;
                //设置<sql/>为<select/>下的第一个节点
                //其实上面写了这么多,就是向<select/>和<include/>节点中插入<sql/>节点
                newInternal.isFirstChild(true);
            }
            else {
            	//在中间插入,和上面的流程一样,向循环链表a,b节点中插入c节点而已
                ChildNode prev = refInternal.previousSibling;
                newInternal.nextSibling = refInternal;
                prev.nextSibling = newInternal;
                refInternal.previousSibling = newInternal;
                newInternal.previousSibling = prev;
            }
        }
    }
	//记录改变次数
    changed();

    if (fNodeListCache != null) {
        if (fNodeListCache.fLength != -1) {
            fNodeListCache.fLength++;
        }
        if (fNodeListCache.fChildIndex != -1) {
         	//如果refInternal(<include/>)是缓存中的第一个节点,则替换当前(<sql/>)为缓存中第一个节点
            if (fNodeListCache.fChild == refInternal) {
                fNodeListCache.fChild = newInternal;
            } else {
            	//否则不设置缓存
                fNodeListCache.fChildIndex = -1;
            }
        }
    }
    //节点插入成功通知
    ownerDocument.insertedNode(this, newInternal, replace);
    checkNormalizationAfterInsert(newInternal);
    return newChild;
} 
ParentNode
public Node removeChild(Node oldChild)    throws DOMException {
	//没有做过多封装,直接调用internalRemoveChild方法
    return internalRemoveChild(oldChild, false);
}
Node internalRemoveChild(Node oldChild, boolean replace)
    throws DOMException {
    CoreDocumentImpl ownerDocument = ownerDocument();
    //替换前较验
    if (ownerDocument.errorChecking) {
        if (isReadOnly()) {
            throw new DOMException(
                        DOMException.NO_MODIFICATION_ALLOWED_ERR,
                        DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null));
        }
        if (oldChild != null && oldChild.getParentNode() != this) {
            throw new DOMException(DOMException.NOT_FOUND_ERR,
                        DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null));
        }
    }
    ChildNode oldInternal = (ChildNode) oldChild;
    //通知文档
    ownerDocument.removingNode(this, oldInternal, replace);

    if (fNodeListCache != null) {
        if (fNodeListCache.fLength != -1) {
            fNodeListCache.fLength--;
        }
        if (fNodeListCache.fChildIndex != -1) {
        	//如果缓存中第一个节点为要被删除节点
            if (fNodeListCache.fChild == oldInternal) {
                fNodeListCache.fChildIndex--;
                //旧节点的上一个节点为缓存中第一个节点
                fNodeListCache.fChild = oldInternal.previousSibling();
            } else {
                fNodeListCache.fChildIndex = -1;
            }
        }
    }
		
	//如果旧节点为第一个节点
    if (oldInternal == firstChild) {
    	//旧节点设置不为第一个节点
        oldInternal.isFirstChild(false);
        //旧节点的下一个节点为第一个节点
        firstChild = oldInternal.nextSibling;
        if (firstChild != null) {
        	//如果当前第一个节点不为空,设置为第一个节点
            firstChild.isFirstChild(true);
            //第一个节点的prev为旧节点的prev
            firstChild.previousSibling = oldInternal.previousSibling;
        }
    } else {
    	//如果要删除的节点不是第一个节点
        ChildNode prev = oldInternal.previousSibling;
        ChildNode next = oldInternal.nextSibling;
        //直接将旧节点的上一个节点next指向下一个节点
        prev.nextSibling = next;
        if (next == null) {
        	//设置第一个节点的上一个节点为旧节点的上一个节点
            firstChild.previousSibling = prev;
        } else {
        	//旧节点的下一个节点的上一个节点为旧节点的上一个节点
            next.previousSibling = prev;
        }
    }
    ChildNode oldPreviousSibling = oldInternal.previousSibling();
    oldInternal.ownerNode       = ownerDocument;
    oldInternal.isOwned(false);
    //被删除节点的上一个节点和下一个节点都置空
    oldInternal.nextSibling     = null;
    oldInternal.previousSibling = null;
    //修改节点的改变次数
    changed();
    ownerDocument.removedNode(this, replace);
    checkNormalizationAfterRemove(oldPreviousSibling);
    return oldInternal;
}

        对于applyIncludes方法的理解,做一个过程展示

1.原始结构
<sql id="a">
    id
</sql>

<sql id="Base_Column_List">
    <include refid="a"/>,username
</sql>

<select id="selectSqlUserById" resultType="User"  >
    select <include refid="Base_Column_List"/> from lz_user  where id=#{id} and is_delete = 0 limit 1
</select>

2. 将<include refid="a"/>标签替换成<sql id="a">...</a>标签
<sql id="Base_Column_List">
  <sql id="a">
    id
  </sql>,username
</sql>

<select id="selectSqlUserById" resultType="User"  >
    select <include refid="Base_Column_List"/> from lz_user  where id=#{id} and is_delete = 0 limit 1
</select>

3.将<sql id="a">...</sql>子标签所有节点id插入到<sql id="a">...</sql>前面
<sql id="Base_Column_List">
  id
  <sql id="a">
    id
  </sql>,username
</sql>

<select id="selectSqlUserById" resultType="User"  >
    select <include refid="Base_Column_List"/> from lz_user  where id=#{id} and is_delete = 0 limit 1
</select>

4. 删除<sql id="a">...</sql>子标签
<sql id="Base_Column_List">
  id,username
</sql>

<select id="selectSqlUserById" resultType="User"  >
    select <include refid="Base_Column_List"/> from lz_user  where id=#{id} and is_delete = 0 limit 1
</select>

5.将<select />标签内<include refid="Base_Column_List"/>替换成<sql id="Base_Column_List">...</sql>
<select id="selectSqlUserById" resultType="User"  >
    select 
    <sql id="Base_Column_List">
      id,username
    </sql> from lz_user  where id=#{id} and is_delete = 0 limit 1
</select>


6.将<sql id="Base_Column_List"/>里的所有节点插入到自己前面
<select id="selectSqlUserById" resultType="User"  >
    select id,username
    <sql id="Base_Column_List">
      id,username
    </sql> from lz_user  where id=#{id} and is_delete = 0 limit 1
</select>


7.删除掉<sql id="Base_Column_List">...</sql>标签
<select id="selectSqlUserById" resultType="User"  >
    select id,username from lz_user  where id=#{id} and is_delete = 0 limit 1
</select>

        经过上面的分析,我们基本理解了applyIncludes()方法执行的过程,但是实际情况可能比这个更加复杂,又有递归调用,如果还没有理解同学,自行下载代码,打断点调试一下吧。

SelectKey标签解析
        在对SelectKey标签解析前,我们先来看看SelectKey标签的用法。
需求: 数据库表中主键一般都是自增的,但是我想让数据库主键每次增加2,那在MyBatis中如何实现呢?请看下面

<insert id="insertUserIdAddDouble" parameterType="User" useGeneratedKeys="true" keyProperty="id" >
    <!--selectKey  会将 SELECT select if(max(id) is null ,1 ,max(id) + 2 ) as newId from lz_user 的结果放入到传入的model的主键里面,
           keyProperty 对应的model中的主键的属性名,这里是 user 中的id,因为它跟数据库的主键对应
           order AFTER 表示 select if(max(id) is null ,1 ,max(id) + 2 ) as newId from lz_user 在insert执行之后执行,多用与自增主键,
                 BEFORE 表示select if(max(id) is null ,1 ,max(id) + 2 ) as newId from lz_user 在insert执行之前执行,这样的话就拿不到主键了,
                       这种适合那种主键不是自增的类型
           resultType 主键类型 -->
    <selectKey keyProperty="id" resultType="long" order="BEFORE">
        select if(max(id) is null ,1 ,max(id) + 2 ) as newId from lz_user
    </selectKey>
    
    insert into lz_user(
    	<if test="id != null">id, </if>
    	<if test="username != null">username, </if>
    	<if test="password != null">password, </if>
    	<if test="realName != null">real_name, </if>
    	<if test="managerId != null">manager_id, </if>
    	is_delete,
    	gmt_create,
    	gmt_modified
    )values(
    	<if test="id != null">#{ id}, </if>
    	<if test="username != null">#{ username}, </if>
    	<if test="password != null">#{ password}, </if>
    	<if test="realName != null">#{ realName}, </if>
    	<if test="managerId != null">#{ managerId}, </if>
    	0,
    	now(),
    	now()
    )
</insert>

        通过上述配置,每次在插入数据库之前,先查询数据库表中id最大值,并+2赋值给User对象的id,就实现了主键每次+2了,接下来,我们来看看MyBatis是如何解析SelectKey标签的,下面来看看SelectKey标签的解析。

XMLStatementBuilder.java
private void processSelectKeyNodes(String id, Class<?> parameterTypeClass, LanguageDriver langDriver) {
    List<XNode> selectKeyNodes = context.evalNodes("selectKey");
    if (configuration.getDatabaseId() != null) {
        parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId());
    }
    parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null);
    //将SelectKey标签转化为Statement存储于Configuration后,从insert节点中删除
    removeSelectKeyNodes(selectKeyNodes);
}

private void parseSelectKeyNodes(String parentId, List<XNode> list, Class<?> parameterTypeClass, LanguageDriver langDriver, String skRequiredDatabaseId) {
	//对select,insert,update,delete标签下所有标签进行遍历
    for (XNode nodeToHandle : list) {
    	//SELECT_KEY_SUFFIX="!selectKey"
    	//而parentId为select ... ,标签的id
        String id = parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX;
        String databaseId = nodeToHandle.getStringAttribute("databaseId");
        if (databaseIdMatchesCurrent(id, databaseId, skRequiredDatabaseId)) {
            parseSelectKeyNode(id, nodeToHandle, parameterTypeClass, langDriver, databaseId);
        }
    }
}

private void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver, String databaseId) {
	//获取resultType,statementType,keyProperty,keyColumn属性
    String resultType = nodeToHandle.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    StatementType statementType = StatementType.valueOf(nodeToHandle.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    String keyProperty = nodeToHandle.getStringAttribute("keyProperty");
    String keyColumn = nodeToHandle.getStringAttribute("keyColumn");
    //判断SelectKey标签里的sql在sql执行前还是执行后执行
    boolean executeBefore = "BEFORE".equals(nodeToHandle.getStringAttribute("order", "AFTER"));

    boolean useCache = false;
    boolean resultOrdered = false;
    KeyGenerator keyGenerator = new NoKeyGenerator();
    Integer fetchSize = null;
    Integer timeout = null;
    boolean flushCache = false;
    String parameterMap = null;
    String resultMap = null;
    ResultSetType resultSetTypeEnum = null;
    SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);
    SqlCommandType sqlCommandType = SqlCommandType.SELECT;
	//将SelectKey构建的Statement存储于当前Mapper中
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
            fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
            resultSetTypeEnum, flushCache, useCache, resultOrdered,
            keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null);

    id = builderAssistant.applyCurrentNamespace(id, false);
	
    MappedStatement keyStatement = configuration.getMappedStatement(id, false);
    //id="com.spring_101_200.test_141_150.test_141_mybatis_usegeneratedkeys_keyproperty.UserMapper.insertUserIdAddDouble!selectKey"
    //id=命名空间+节点id+!SelectKey 组成,存储于configuration
    configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
}

private void removeSelectKeyNodes(List<XNode> selectKeyNodes) {
    for (XNode nodeToHandle : selectKeyNodes) {
        nodeToHandle.getParent().getNode().removeChild(nodeToHandle.getNode());
    }
}
XMLLanguageDriver.java
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    //解析sql脚本
    return builder.parseScriptNode();
}
public SqlSource parseScriptNode() {
	//解析是否有${}标识
    List<SqlNode> contents = parseDynamicTags(context);
    MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
    SqlSource sqlSource = null;
    if (isDynamic) {
    	//对于动态sql,也就是SQL中有${}或<if/>,<trim/>,<where/>,<set/>,<foreach/> 
    	//等元素,则为动态SQL,动态SQL只是将节点保存起来,在解析阶段没有做过多的处理,
    	//但是里面的getBoundSql()方法,我们在后面的博客中再来详述了
        sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
        sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
}
private List<SqlNode> parseDynamicTags(XNode node) {
    List<SqlNode> contents = new ArrayList<SqlNode>();
    //获取当前节点下的所有子节点,如获取<select>下的所有元素
    NodeList children = node.getNode().getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
        XNode child = node.newXNode(children.item(i));
        //如果当前节点是<![CDATA[ order_date <= #{endTime,jdbcType=DATE}  ]]>或纯文本
        if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
        	//获取如<select/>标签内的SQL
            String data = child.getStringBody("");
            TextSqlNode textSqlNode = new TextSqlNode(data);
            //判断当前SQL中是否在${}包含的元素,如果有,则是动态SQL
            if (textSqlNode.isDynamic()) {
                contents.add(textSqlNode);
                isDynamic = true;
            } else {
            	//如果SQL中没有${}包含的元素,创建StaticTextSqlNode
                contents.add(new StaticTextSqlNode(data));
            }
        } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
            String nodeName = child.getNode().getNodeName();
            //如果当前节点下是<trim/>,<where/>,<set/>,<foreach/>,<if/>,<choose/>,<when/>,<otherwise/>,<bind/>
            NodeHandler handler = nodeHandlers.get(nodeName);
            if (handler == null) {
                throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
            }
            //获取标签的handler处理Node
            handler.handleNode(child, contents);
            isDynamic = true;
        }
    }
    return contents;
}
RawSqlSource.java
public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
    this(configuration, getSql(configuration, rootSqlNode), parameterType);
}

public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> clazz = parameterType == null ? Object.class : parameterType;
    //sql解析
    sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<String, Object>());
}
private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
    DynamicContext context = new DynamicContext(configuration, null);
    //调用节点的apply方法
    rootSqlNode.apply(context);
    return context.getSql();
}

        SqlNode有很多实现类,如ChooseSqlNode,ForEachSqlNode,IfSqlNode,SetSqlNode,StaticTextSqlNode等,其中StaticTextSqlNode类apply实现非常简单,直接在SQL后面加上空格就可以了。当然,感兴趣的小伙伴可以去研究一下其他Node的apply()方法实现。让你有意想不到的收获。

StaticTextSqlNode.java
public boolean apply(DynamicContext context) {
    context.appendSql(text);
    return true;
}
public void appendSql(String sql) {
    sqlBuilder.append(sql);
    sqlBuilder.append(" ");
}
SqlSourceBuilder.java
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    GenericTokenParser parser= new GenericTokenParser("#{", "}", handler);
    String sql = parser. parse(originalSql);
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}

        在调用parse()方法之前,先说明一下,上述方法的构造函数如下:

public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
    this.openToken = openToken;
    this.closeToken = closeToken;
    this.handler = handler;
}

        因此在调用parse()方法时,openToken为${,closeToken为},handler为ParameterMappingTokenHandler实例。

GenericTokenParser.java
public String parse(String text) {
    StringBuilder builder = new StringBuilder();
    //当前SQL不为空
    if (text != null && text.length() > 0) {
        char[] src = text.toCharArray();
        int offset = 0;
        int start = text.indexOf(openToken, offset);
        //判断当前sql中是否有 #{ 
        while (start > -1) {
            if (start > 0 && src[start - 1] == '\\') {
                //变量被转义。删除反斜杠 
                builder.append(src, offset, start - offset - 1).append(openToken);
                offset = start + openToken.length();
            } else {
                int end = text.indexOf(closeToken, start);
                if (end == -1) {
                	//如果SQL中只有${ ,没有},如
                	//select * from lz_user where id = 1#{1
                	//直接拼接整个SQL
                    builder.append(src, offset, src.length - offset);
                    //将解析下标移动到最后,解析完成
                    offset = src.length;
                } else {
                	//拼接#{前的sql
                    builder.append(src, offset, start - offset);
                    offset = start + openToken.length();
                    //截取#{ }之间的文本
                    String content = new String(src, offset, end - offset);
                    //将#{XXX}替换成?,handleToken还是做了很多事情的
                    builder.append(handler.handleToken(content));
                    //解析下标略过 } 
                    offset = end + closeToken.length();
                }
            }
            start = text.indexOf(openToken, offset);
        }
        //如果没有指定前缀,拼接之前的SQL返回
        if (offset < src.length) {
            builder.append(src, offset, src.length - offset);
        }
    }
    return builder.toString();
}
SqlSourceBuilder.java
public String handleToken(String content) {
    parameterMappings.add(buildParameterMapping(content));
    return "?";
}

        在这里,我们可能看到,SQL 【select * from lz_user where id = #{xxx} and username = #{YYY}】中的#{xxx}和#{YYY}最终都会被替换成 ? ,而buildParameterMapping()方法主要是对参数映射

private ParameterMapping buildParameterMapping(String content) {
	//在parseParameterMapping()方法调用前,我们先来看看#{}配置如何使用,在正常的情况下,
	//我们一般在#{}里配置的是:#{id},#{username} 或#{password,jdbcType=VARCHAR}
	//但源码比我们想得更多,如
	//#{(a?1:2),userName:VARCHAR,javaType=java.util.Long,numericScale=2,resultMap=xxx,typeHandler=MyTypeHandler,jdbcTypeName=long}
	//这样配置,我们将得到如下的propertiesMap
	//{"userName:VARCHAR,javaType":"java.util.Long","expression":"a?1:2","numericScale":"2","typeHandler":"MyTypeHandler","jdbcTypeName":"long","resultMap":"xxx"}
	//内部是如何生成Map的,有兴趣同学可以去研究一下生成Map的算法
    Map<String, String> propertiesMap = parseParameterMapping(content);
    String property = propertiesMap.get("property");
    Class<?> propertyType;
    if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
        propertyType = metaParameters.getGetterType(property);
    } else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
    	//基本数据类型处理,如
    	//<select id="getUser" parameterType="java.lang.Long"  resultType="User"/>
    	//typeHandlerRegistry先会到TypeHandlerRegistry的Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP 属性中查的,而TYPE_HANDLER_MAP属性中注册了很多基本类型的处理器
    	//public TypeHandlerRegistry() {
    	//	register(Boolean.class, new BooleanTypeHandler());
    	//	register(boolean.class, new BooleanTypeHandler());
    	// 	register(JdbcType.BOOLEAN, new BooleanTypeHandler());
    	//	register(JdbcType.BIT, new BooleanTypeHandler());

    	//	register(byte.class, new ByteTypeHandler());
    	//	register(JdbcType.TINYINT, new ByteTypeHandler());
    	//	...
		//}
		//因此,如果是基本类型,直接赋值
        propertyType = parameterType;
    } else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
    	//如果像这样配置#{id,jdbcType=CURSOR},则设置propertyType为ResultSet类型
        propertyType = java.sql.ResultSet.class;
    } else if (property != null) {
    	//<select id="getUserByUser" parameterType="com.spring_101_200.test_141_150.test_147_mybatis_constructorargs.User"
            resultType="com.spring_101_200.test_141_150.test_147_mybatis_constructorargs.User"/>
        //对于这种自定义类型情况,因为MyBatis没有提供typeHandler,因此,只能通过反射来获取参数类型了
        MetaClass metaClass = MetaClass.forClass(parameterType);
        if (metaClass.hasGetter(property)) {
            propertyType = metaClass.getGetterType(property);
        } else {
            propertyType = Object.class;
        }
    } else {
    	//如果都不符合条件,默认是Object类型了
        propertyType = Object.class;
    }
    ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
    Class<?> javaType = propertyType;
    String typeHandlerAlias = null;
    //下面主要是将parseParameterMapping参数设置到ParameterMapping中
    for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
        String name = entry.getKey();
        String value = entry.getValue();
        if ("javaType".equals(name)) {
            javaType = resolveClass(value);
            builder.javaType(javaType);
        } else if ("jdbcType".equals(name)) {
            builder.jdbcType(resolveJdbcType(value));
        } else if ("mode".equals(name)) {
            builder.mode(resolveParameterMode(value));
        } else if ("numericScale".equals(name)) {
            builder.numericScale(Integer.valueOf(value));
        } else if ("resultMap".equals(name)) {
            builder.resultMapId(value);
        } else if ("typeHandler".equals(name)) {
            typeHandlerAlias = value;
        } else if ("jdbcTypeName".equals(name)) {
            builder.jdbcTypeName(value);
        } else if ("property".equals(name)) {
            // Do Nothing
        } else if ("expression".equals(name)) {
        	//如果#{(xxx)}将抛出异常
            throw new BuilderException("Expression based parameters are not supported yet");
        } else {
            throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + "}.  Valid properties are " + parameterProperties);
        }
    }
    if (typeHandlerAlias != null) {
        builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
    }
    return builder.build();
}

在这里插入图片描述

        经过层层解析,我们终于得到了sqlSource,在sqlSource中将SQL中的#{}包含的元素替换成? ,同时得到SQL参数parameterMapping,
        经过上面的分析,我们己经将Mapper.xml中xml解析完毕,下面来看看bindMapperForNamespace()这个方法,顾名思义通过命名空间绑定Mapper,也就是将Mapper.xml和Mapper绑定起来,那下面来看看,Mapper.xml和Mapper文件是如何绑定的。

XMLMapperBuilder.java
private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
        Class<?> boundType = null;
        try {
        	//将命名空间转化为类
            boundType = Resources.classForName(namespace);
        } catch (ClassNotFoundException e) {
        }
        if (boundType != null) {
        	//configuration中不存在此Mapper类
            if (!configuration.hasMapper(boundType)) {
            	//存储命名空间到configuration的loadedResources属性中,这个主要是做一个开关
            	//如果Mapper.java被解析,但是Mapper.xml还没有被加载解析,先加载解析Mapper.xml文件
                configuration.addLoadedResource("namespace:" + namespace);
                //保存当前Mapper到configuration的knownMappers中
                configuration.addMapper(boundType);
            }
        }
    }
}
Configuration.java
public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
}
MapperRegistry.java
public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
        if (hasMapper(type)) {
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
        }
        boolean loadCompleted = false;
        try {
        	//注册Mapper代理工厂,【这一步非常重要,在MapperProxyFactory类的getMapper()方法中,创建JDK动态代理】
            knownMappers.put(type, new MapperProxyFactory<T>(type));
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            //Mapper注解解析器解析
            parser.parse();
            loadCompleted = true;
        } finally {
        	//如果解析失败,重新解析
            if (!loadCompleted) {
                knownMappers.remove(type);
            }
        }
    }
}
public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
    	//如果Mapper.xml
        loadXmlResource();
        //避免重复解析
        configuration.addLoadedResource(resource);
        //设置当前命名空间为Mapper名
        assistant.setCurrentNamespace(type.getName());
        //如果配置了CacheNamespace注解,为当前命名空间添加缓存类
        parseCache();
        //如果配置了CacheNamespaceRef注解,获取引用类型的缓存对象为当前Mapper缓存
        parseCacheRef();
        Method[] methods = type.getMethods();
        for (Method method : methods) {
            try {
                if (!method.isBridge()) { // issue #237
                	//对Mapper中所有方法遍历
                    parseStatement(method);
                }
            } catch (IncompleteElementException e) {
                configuration.addIncompleteMethod(new MethodResolver(this, method));
            }
        }
    }
    parsePendingMethods();
}
void parseStatement(Method method) {
    Class<?> parameterTypeClass = getParameterType(method);
    LanguageDriver languageDriver = getLanguageDriver(method);
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    //如果方法中没有配置Select,Insert,Update,Delete,SelectProvider,
    //InsertProvider,UpdateProvider,DeleteProvider中的任意一种注解,sqlSource为null, 不做解析
    if (sqlSource != null) {
    	//解析Options注解参数
        Options options = method.getAnnotation(Options.class);
        final String mappedStatementId = type.getName() + "." + method.getName();
        Integer fetchSize = null;
        Integer timeout = null;
        StatementType statementType = StatementType.PREPARED;
        ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
        SqlCommandType sqlCommandType = getSqlCommandType(method);
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        //非select ,flushCache为true
        boolean flushCache = !isSelect;
        //如果是select使用本地缓存
        boolean useCache = isSelect;

        KeyGenerator keyGenerator;
        String keyProperty = "id";
        String keyColumn = null;
        //如果当前方法是insert或update
        if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
        	//是否配置了SelectKey注解
            SelectKey selectKey = method.getAnnotation(SelectKey.class);
            if (selectKey != null) {
            	//对于SelectKey的处理和XML一样,也是获取注解SelectKey的statement,keyProperty,keyColumn,
            	//before,resultType,statementType等属性封装成SelectKeyGenerator存储到configuration中
                keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
                keyProperty = selectKey.keyProperty();
            } else {
                if (options == null) {
                	//如果没有配置Options参数,则看全局配置中是否配置了useGeneratedKeys属性,如果配置了使用Jdbc3KeyGenerator键生成器
                    keyGenerator = configuration.isUseGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
                } else {
                	//如果配置了options,则看options中是否配置了useGeneratedKeys值
                    keyGenerator = options.useGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
                    keyProperty = options.keyProperty();
                    keyColumn = options.keyColumn();
                }
            }
        } else {
        	//如果不是insert或update,键生成器默认为NoKeyGenerator
            keyGenerator = new NoKeyGenerator();
        }
        
        if (options != null) {
        	//如果配置了options属性,则获取flushCache,useCache,fetchSize,timeout,
        	//statementType,resultSetType注解属性
            flushCache = options.flushCache();
            useCache = options.useCache();
            fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
            timeout = options.timeout() > -1 ? options.timeout() : null;
            statementType = options.statementType();
            resultSetType = options.resultSetType();
        }

        String resultMapId = null;
        ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
        if (resultMapAnnotation != null) {
            String[] resultMaps = resultMapAnnotation.value();
            StringBuilder sb = new StringBuilder();
            for (String resultMap : resultMaps) {
                if (sb.length() > 0) sb.append(",");
                sb.append(resultMap);
            }
            resultMapId = sb.toString();
        } else if (isSelect) {//select类型resultMap处理
        	//主要对于ConstructorArgs,Results,TypeDiscriminator,Case等注解的封装处理
            resultMapId = parseResultMap(method);
        }
        assistant.addMappedStatement(
                mappedStatementId,
                sqlSource,
                statementType,
                sqlCommandType,
                fetchSize,
                timeout,
                null,                             // ParameterMapID
                parameterTypeClass,
                resultMapId,    // ResultMapID
                getReturnType(method),
                resultSetType,
                flushCache,
                useCache,
                false, // TODO issue #577
                keyGenerator,
                keyProperty,
                keyColumn,
                null,
                languageDriver,
                null);
    }
}

        经过源码的分析,我们知道了,在MyBatis中,Mapper.xml中的配置和Mapper中的注解是不能合并使用的,要么你完全使用Mapper.xml,要么你用Mapper,不能两者结合起来使用。
如:

@ResultType(User.class)
User getUser(@Param("id") long id);

<select id="getUser" >
    select * from lz_user where id = #{id}
</select>

        你不能使用Mapper.xml中的SQL,同时使用Mapper中的ResultType,这样会抛出异常,从源码中也看到,是不允许的,当发现Mapper中没有配置Select,Insert,Update,Delete,SelectProvider,InsertProvider,UpdateProvider,DeleteProvider注解时,直接略过。
在这里插入图片描述

XMLScriptBuilder.java
private Map<String, NodeHandler> nodeHandlers = new HashMap<String, NodeHandler>() {
    private static final long serialVersionUID = 7123056019193266281L;

    {
    	//注册trim,where,set,foreach,if,choose,when,otherwise,bind标签处理器
        put("trim", new TrimHandler());
        put("where", new WhereHandler());
        put("set", new SetHandler());
        put("foreach", new ForEachHandler());
        put("if", new IfHandler());
        put("choose", new ChooseHandler());
        put("when", new IfHandler());
        put("otherwise", new OtherwiseHandler());
        put("bind", new BindHandler());
    }
};

private interface NodeHandler {
    void handleNode(XNode nodeToHandle, List<SqlNode> targetContents);
}

private class BindHandler implements NodeHandler {
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
    	//获取bind标签的name和value
        final String name = nodeToHandle.getStringAttribute("name");
        final String expression = nodeToHandle.getStringAttribute("value");
        //构建name和expression
        final VarDeclSqlNode node = new VarDeclSqlNode(name, expression);
        targetContents.add(node);
    }
}

private class TrimHandler implements NodeHandler {
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
    	/解析trim下的其他动态标签
        List<SqlNode> contents = parseDynamicTags(nodeToHandle);
        MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
        //获取trim的prefix,prefixOverrides,suffix,suffixOverrides等属性
        String prefix = nodeToHandle.getStringAttribute("prefix");
        String prefixOverrides = nodeToHandle.getStringAttribute("prefixOverrides");
        String suffix = nodeToHandle.getStringAttribute("suffix");
        String suffixOverrides = nodeToHandle.getStringAttribute("suffixOverrides");
        TrimSqlNode trim = new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides);
        targetContents.add(trim);
    }
}

private class WhereHandler implements NodeHandler {
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
        List<SqlNode> contents = parseDynamicTags(nodeToHandle);
        MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
        WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
        targetContents.add(where);
    }
}

private class SetHandler implements NodeHandler {
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
        List<SqlNode> contents = parseDynamicTags(nodeToHandle);
        MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
        SetSqlNode set = new SetSqlNode(configuration, mixedSqlNode);
        targetContents.add(set);
    }
}

private class ForEachHandler implements NodeHandler {
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
        List<SqlNode> contents = parseDynamicTags(nodeToHandle);
        MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
        String collection = nodeToHandle.getStringAttribute("collection");
        String item = nodeToHandle.getStringAttribute("item");
        String index = nodeToHandle.getStringAttribute("index");
        String open = nodeToHandle.getStringAttribute("open");
        String close = nodeToHandle.getStringAttribute("close");
        String separator = nodeToHandle.getStringAttribute("separator");
        ForEachSqlNode forEachSqlNode = new ForEachSqlNode(configuration, mixedSqlNode, collection, index, item, open, close, separator);
        targetContents.add(forEachSqlNode);
    }
}

private class IfHandler implements NodeHandler {
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
        List<SqlNode> contents = parseDynamicTags(nodeToHandle);
        MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
        String test = nodeToHandle.getStringAttribute("test");
        IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
        targetContents.add(ifSqlNode);
    }
}

private class OtherwiseHandler implements NodeHandler {
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
        List<SqlNode> contents = parseDynamicTags(nodeToHandle);
        MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
        targetContents.add(mixedSqlNode);
    }
}

private class ChooseHandler implements NodeHandler {
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
        List<SqlNode> whenSqlNodes = new ArrayList <SqlNode>();
        List<SqlNode> otherwiseSqlNodes = new ArrayList<SqlNode>();
        handleWhenOtherwiseNodes(nodeToHandle, whenSqlNodes, otherwiseSqlNodes);
        SqlNode defaultSqlNode = getDefaultSqlNode(otherwiseSqlNodes);
        ChooseSqlNode chooseSqlNode = new ChooseSqlNode(whenSqlNodes, defaultSqlNode);
        targetContents.add(chooseSqlNode);
    }

    private void handleWhenOtherwiseNodes(XNode chooseSqlNode, List<SqlNode> ifSqlNodes, List<SqlNode> defaultSqlNodes) {
        List<XNode> children = chooseSqlNode.getChildren();
        for (XNode child : children) {
            String nodeName = child.getNode().getNodeName();
            NodeHandler handler = nodeHandlers.get(nodeName);
            if (handler instanceof IfHandler) {
                handler.handleNode(child, ifSqlNodes);
            } else if (handler instanceof OtherwiseHandler) {
                handler.handleNode(child, defaultSqlNodes);
            }
        }
    }

    private SqlNode getDefaultSqlNode(List<SqlNode> defaultSqlNodes) {
        SqlNode defaultSqlNode = null;
        if (defaultSqlNodes.size() == 1) {
            defaultSqlNode = defaultSqlNodes.get(0);
        } else if (defaultSqlNodes.size() > 1) {
            throw new BuilderException("Too many default (otherwise) elements in choose statement.");
        }
        return defaultSqlNode;
    }
}

        对MyBatis熟悉的同学,我相信一眼就能看出代码的意思,但是这些Handler的巧妙之处就是不断递归调用parseDynamicTags()方法,像剥洋葱一样,不层层向里剥,即使再复杂的SQL表达式最终都会被解析为MixedSqlNode(contents) + 标签(如if标签的属性参数为test)属性参数的形式。而contents又是SqlNode的List集合,而SqlNode又是由MixedSqlNode(contents) + 标签属性参数组成,因此最终会形成一棵树,说得有点晕,来看个例子吧。在Mapper.xml中配置如下表达式

<select id="getUserByCompx"  resultType="User">
        select * from lz_user
        <where>
            <if test="id != null">
                and id = #{id}
            </if>
            <if test="userNameList !=null ">
                or  username in
                <foreach item="item" collection="userNameList"  open="(" separator="," close=")">
                    <if test="isDelete !=null">
                        #{item}
                    </if>
                </foreach>
            </if>

        </where>
</select>

在这里插入图片描述
        从结果来看,整个执行流程就不言而遇了。

parsePendingResultMaps方法使用场景
        了解parsePendingResultMaps()方法使用场景之前,我们先来思考一个问题,在MyBatis的Mapper.xml文件中,resultMap标签是允许继承其他的resultMap标签的,如果是相同的Mapper.xml文件相互继承还好说,如果是不同的Mapper.xml文件,如B中的ResultMap标签继承A中的ResultMap标签,但是B比A先加载,B在加载时,在Configuration的resultMaps找不到A的resultMap,就需要用到下面的方法来解决此问题了。
下面来看一个例子

  1. 修改mybatis-config.xml
<mappers>
    <mapper resource="spring_101_200/config_141_150/spring145_mybatis_cachenamespaceref_xml/UserBillMapper.xml"></mapper>
    <mapper resource="spring_101_200/config_141_150/spring145_mybatis_cachenamespaceref_xml/UserMapper.xml"></mapper>
</mappers>

目的是让UserBillMapper.xml比UserMapper.xml先加载

  1. 在UserMapper.xml添加resultMap标签
<resultMap id="UserBaseResultMap" type="User">
    <id column="id" property="id"/>
    <result column="is_delete" property="isDelete"/>
    <result column="gmt_create" property="gmtCreate"/>
    <result column="gmt_modified" property="gmtModified"/>
    <result column="username" property="username"/>
    <result column="password" property="password"/>
    <result column="real_name" property="realName"/>
    <result column="manager_id" property="managerId"/>
</resultMap>

  1. 在UserBillMapper.xml中添加resultMap标签继承UserMapper.xml中的UserBaseResultMap标签
<resultMap id="UserBillBaseResultMap" type="User" extends="com.spring_101_200.test_141_150.test_145_mybatis_cachenamespaceref_xml.UserMapper.UserBaseResultMap">
</resultMap>
  1. 测试
    在这里插入图片描述
XMLMapperBuilder.java
private void parsePendingResultMaps() {
    Collection<ResultMapResolver> incompleteResultMaps = configuration.getIncompleteResultMaps();
    synchronized (incompleteResultMaps) {
        Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator();
        while (iter.hasNext()) {
            try {
                iter.next().resolve();
                iter.remove();
            } catch (IncompleteElementException e) {
                // ResultMap is still missing a resource...
                //抛出异常,不做处理
            }
        }
    }
}
  1. 结果分析

        在addResultMap()方法执行中,如果Configuration的resultMaps中没有当前ResultMap继承的ResultMap,将抛出IncompleteElementException异常,并将当前的ResultMap对应的ResultMapResolver存储于Configuration中的incompleteResultMaps属性中,每个Mapper.xml解析完成后,再迭代incompleteResultMaps中的ResultMapResolver,调用其resolve()方法来完成ResultMap的注册,在本例中,在UserBillMapper.xml解析时,先去Configuration中找com…UserMapper.UserBaseResultMap对应的ResultMap,因为UserMapper.xml在后面解析,所以没有找到对应的ResultMap,将抛出IncompleteElementException异常,因此UserBillBaseResultMap对应的ResultMapResolver存储于Configuration中的incompleteResultMaps,当UserBillMapper.xml解析结束后调用parsePendingResultMaps()方法,发现Configuration中的resultMaps还是没有UserBaseResultMap对应的ResultMap,在parsePendingResultMaps()方法中继续抛出IncompleteElementException异常,但是parsePendingResultMaps()方法对IncompleteElementException不做处理,解析继续,当解析完UserMapper.xml后,UserBaseResultMap对应的ResultMap存储于Configuration的resultMaps中,接着调用parsePendingResultMaps()方法,发现incompleteResultMaps中存在ResultMapResolver元素,调用其resolve()方法,此时发现UserBaseResultMap对应的ResultMap己经存在,则将UserBillBaseResultMap对应的ResultMap加入到Configuration的resultMaps中。因此,在看 iter.next().resolve();这段代码时,你会发现调用了两次,每解析完一个Mapper.xml都会调用,但是只有解析完UserMapper.xml时,调用resolve()方法才没有抛出异常,UserBillBaseResultMap对应的ResultMapResolver才从Configuration中的incompleteResultMaps中移除掉。

parsePendingChacheRefs方法使用场景
        其实parsePendingChacheRefs()的实现原理和parsePendingResultMaps()实现一样,下面我们来看看这个例子吧。

  1. 同样修改mybatis-config.xml
<mappers>
    <mapper resource="spring_101_200/config_141_150/spring145_mybatis_cachenamespaceref_xml/UserBillMapper.xml"></mapper>
    <mapper resource="spring_101_200/config_141_150/spring145_mybatis_cachenamespaceref_xml/UserMapper.xml"></mapper>
</mappers>

让UserBillMapper.xml在UserMapper.xml先解析

  1. 在UserBillMapper.xml添加cache-ref标签
    <cache-ref namespace="com.spring_101_200.test_141_150.test_145_mybatis_cachenamespaceref_xml.UserMapper"/>
  1. 在UserMapper.xml添加cache标签
<cache type="com.spring_101_200.test_141_150.test_145_mybatis_cachenamespaceref_xml.MybatisPlusCache">
	<!--eviction(收回策略)
        LRU(最近最少使用的):移除最长时间不被使用的对象,这是默认值。
        FIFO(先进先出):按对象进入缓存的顺序来移除它们。
        SOFT(软引用):移除基于垃圾回收器状态和软引用规则的对象。
        WEAK(弱引用):更积极地移除基于垃圾收集器状态和弱引用规则的对象。
    -->
    <property name="eviction" value="LRU" />
    <!--flushinterval(刷新间隔)
        可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况不设置,即没有刷新间隔,缓存仅仅在调用语句时刷新。-->
    <property name="flushInterval" value="6000000" />
     <!--size(引用数目)
        可以被设置为任意正整数,要记住缓存的对象数目和运行环境的可用内存资源数目。默认值是1024 。-->
    <property name="size" value="1024" />
    
     <!--readOnly(只读)
        属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改,这提供了很重要的性能优势。
        可读写的缓存会通过序列化返回缓存对象的拷贝,这种方式会慢一些,但是安全,因此默认是 false。-->
    <property name="readOnly" value="false" />
</cache>
  1. 测试
    在这里插入图片描述
XMLMapperBuilder.java
private void parsePendingChacheRefs() {
    Collection<CacheRefResolver> incompleteCacheRefs = configuration.getIncompleteCacheRefs();
    synchronized (incompleteCacheRefs) {
        Iterator<CacheRefResolver> iter = incompleteCacheRefs.iterator();
        while (iter.hasNext()) {
            try {
                iter.next().resolveCacheRef();
                iter.remove();
            } catch (IncompleteElementException e) {
                // Cache ref is still missing a resource...
                //抛出异常,不做处理
            }
        }
    }
}
  1. 结果分析

        当解析UserBillMapper.xml文件cache-ref标签时,在Configuration的caches里没有找到对应的Cache,抛出的IncompleteElementException异常,MyBatis将cache-ref对应的CacheRefResolver存储到Configuration的incompleteCacheRefs中,每解析完一个Mapper.xml将调用parsePendingChacheRefs()方法,看Configuration中的incompleteCacheRefs集体中有没有需要迭代的CacheRefResolver,有则调用其resolveCacheRef()方法,这个例子中,当UserBillMapper.xml解析cache-ref标签时,因为找不到UserBillMapper对应的Cache抛出IncompleteElementException异常,而cache-ref对应的CacheRefResolver被存储到Configuration的incompleteCacheRefs中,当解析完调用parsePendingChacheRefs()方法,因为找到对应的Cache,依然抛出IncompleteElementException异常,因为在parsePendingChacheRefs()中对IncompleteElementException不做处理,因此看不到异常信息,当解析UserMapper.xml完时,仍然调用parsePendingChacheRefs()方法,此时,Configuration中己经有cache-ref对应的Cache了,因此调用CacheRefResolver的resolveCacheRef()方法时,cache-ref被设置成功,没有抛出异常,CacheRefResolver从Configuration中的incompleteCacheRefs被remove掉,cache-ref标签解析完毕,这里另外提一下的是我觉得parsePendingChacheRefs()这个方法看起来有违和感,中间多了个h,应该写成parsePendingChaceRefs()才觉得对。可能是MyBatis没有注意到的吧。

parsePendingStatements()方法使用场景分析
        对于parsePendingStatements的使用场景和上述两个方法是一样,不过,我们还是来举个例子吧。

  1. 修改mybatis-config.xml文件
<mappers>
    <mapper resource="spring_101_200/config_141_150/spring145_mybatis_cachenamespaceref_xml/UserBillMapper.xml"></mapper>
    <mapper resource="spring_101_200/config_141_150/spring145_mybatis_cachenamespaceref_xml/UserMapper.xml"></mapper>
</mappers>

让UserBillMapper.xml在UserMapper.xml前先解析

  1. 在UserBillMapper.xml添加
<select id="getUserByMap" parameterType="java.lang.Long" resultMap="com.spring_101_200.test_141_150.test_145_mybatis_cachenamespaceref_xml.UserMapper.UserBaseResultMap">
     select * from lz_user where id=#{id}
</select>

使用了UserMapper.xml中的UserBaseResultMap

  1. 在UserMapper.xml中添加UserBaseResultMap
<resultMap id="UserBaseResultMap" type="User">
    <id column="id" property="id"/>
    <result column="is_delete" property="isDelete"/>
    <result column="gmt_create" property="gmtCreate"/>
    <result column="gmt_modified" property="gmtModified"/>
    <result column="username" property="username"/>
    <result column="password" property="password"/>
    <result column="real_name" property="realName"/>
    <result column="manager_id" property="managerId"/>
</resultMap>

4.测试
在这里插入图片描述

XMLMapperBuilder.java
private void parsePendingStatements() {
    Collection<XMLStatementBuilder> incompleteStatements = configuration.getIncompleteStatements();
    synchronized (incompleteStatements) {
        Iterator<XMLStatementBuilder> iter = incompleteStatements.iterator();
        while (iter.hasNext()) {
            try {
                iter.next().parseStatementNode();
                iter.remove();
            } catch (IncompleteElementException e) {
                // Statement is still missing a resource...
            }
        }
    }
}

结果分析和上面两个一样,有兴趣的同学自行分析一下吧。

总结:

        经过一个一个标签分析,相信你对MyBatis的Mapper.xml文件解析和注解配置的解析,有了一定的理解,不过这篇博客纯属个人看法,如果将来我的认知更加深刻,说不定再回来修改这一块的内容。费话不多说,下面来总结一下解析流程吧。

流程:
1)configurationElement(): 对Mapper.xml下的所有mapper标签解析
	1)cacheRefElement():对cache-ref标签解析,设置builderAssistant的cache为ref对应Mapper的cache
	2)cacheElement():对cache解析,设置相应的Cache到builderAssistant中,并存储到Configuration的caches属性中
	3)parameterMapElement():解析parameterMap标签,结果存储到Configuration的parameterMaps中
	4)resultMapElements():解析resultMap标签,并将结果存储到Configuration的resultMaps属性中
	5)sqlElement():解析sql标签,并将结果存储到当前Mapper的sqlFragments属性中
	6)buildStatementFromContext():对select|insert|update|delete标签解析
		1)parseStatementNode():对Node解析
			1)applyIncludes():处理include标签
			2)processSelectKeyNodes():处理标签下的SelectKey标签
			3)createSqlSource():创建SqlSource
				1)parseScriptNode():解析标签下的SQL
					1)isDynamic():判断当前SQL中是否有${},<sql/>等,有则是动态SQL,没有则Raw SQL
					2)new DynamicSqlSource():创建动态SQL Source
					3)new RawSqlSource():创建Row SQL Source
						1)parse():SQL解析
							1)new GenericTokenParser("#{", "}", handler):设置openToken,closeToken,handler
							2)parse():如果SQL中有 #{} 元素,替换成 ?
								1)handleToken():构建ParameterMapping 存储于parameterMappings中
			4)addMappedStatement():构建Statement存储到Configuration的mappedStatements中
2)bindMapperForNamespace(): 对于Mapper.java中配置了注解Statement构建
	1)addMapper(boundType):获取Mapper.xml命名空间对应的Mapper类,构建Statement
		1)parse():解析Mapper
			1)loadXmlResource():如果在解析Mapper之前,Mapper.xml还没有解析,先解析Mapper.xml
			2)parseCache():解析@CacheNamespace注解
				1)useNewCache():创建新Cache
			3)parseCacheRef():解析@CacheNamespaceRef注解
				1)useCacheRef():引用Configuration中己经存在的Cache
			4)parseStatement(method):遍历Mapper中每个方法调用parseStatement方法构建Statement
				1)getSqlSourceFromAnnotations(): 判断方法中有没有Select,Insert,Update,Delete,SelectProvider,InsertProvider,UpdateProvider,DeleteProvider注解,如果有获取sqlSource,而这里的sqlSource和Mapper.xml sqlSource获取方式类似,这里不再赘述
				2)handleSelectKeyAnnotation():对@SelectKey注解处理
				3)parseResultMap():对resultMap处理,方法内部对ConstructorArgs,Results,TypeDiscriminator,Arg 等注解的处理,和Mapper.xml的解析手法类似
				4)addMappedStatement():将方法构建的Statement存储于Configuration的mappedStatements中。
3)parsePendingResultMaps():对于当前Mapper.xml的resultMap继承其他Mapper.xml中的resultMap,但是其他Mapper.xml还没有被解析的情况处理。
4)parsePendingChacheRefs():对于当前Mapper.xml的cache-ref引用其他Mapper.xml中的cache,但是其他Mapper.xml还没有被解析的情况处理。
5)parsePendingStatements():对于当前Mapper.xml的select|update|insert|delete引用其他Mapper.xml中的ResultMap,但是其他Mapper.xml还没有被解析的情况处理。

        本篇博客着重对Mapper.xml文件解析,对注解解析相对而言弱一点,有兴趣的同学可以去研究一下,注解是如何解析的。

问题?

        在前面提到过,Mapper.xml的解析和Mapper类解析生成Statement是相对独立的,没有什么交集,但是下面情况例外:

UserMapper.java
    void updateRealName(@Param("id") long id, @Param("realName") String realName);

UserMapper.xml
<update id="updateRealName">
    update lz_user set real_name = #{realName} where id = #{id}
</update>

        parameterType没有配置到Mapper.xml中,而是用@Param注解替代,毋庸置疑不会报错,但是其实现原理是什么呢?有兴趣的同学可以研究一下。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值