我们在第一篇博客中己经对MyBatis基本配置做了详细的解析,细心的读者可能会发现有一个重要的文件映射器没有做详细的分析,今天就来对映射器做一个具体分析吧。在第二篇博客中,我们己经对映射器的使用使用了足够多的例子来说明。映射器解析的关键方法是mapperElement(),下面来做分析吧。
引入映射器的方式有很多种,一般分成以下几种
- 用 userMapper.xml引入映射器
<mappers> <mapper resource="spring_101_200/config_131_140/spring132_mybatis_typehandlers/UserMapper.xml"></mapper> </mappers>
- 用包名引入映射器
<mappers> <package name="com.spring_101_200.test_131_140.test_132_mybatis_typehandlers"/> </mappers>
- 用类注册引入映射器,但是 Mapper和Mapper.xml必需放到一个目录下
<mappers> <mapper class="com.spring_101_200.test_131_140.test_132_mybatis_typehandlers.UserMapper"></mapper>
- 用文件路径引入映射器
<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();
- 创建 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; }
- 创建 Mapper
public interface UserMapper { User getUser(@Param("id") Long id); } public interface UserBillMapper { User getUser(@Param("id") Long id); }
- 创建 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>
- 创建自定义缓存类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一样,将使用相同的缓存 。
- 测试
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); }
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,就需要用到下面的方法来解决此问题了。
下面来看一个例子
- 修改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先加载
- 在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>
- 在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>
- 测试
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... //抛出异常,不做处理 } } } }
结果分析
在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()实现一样,下面我们来看看这个例子吧。
- 同样修改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先解析
- 在UserBillMapper.xml添加cache-ref标签
<cache-ref namespace="com.spring_101_200.test_141_150.test_145_mybatis_cachenamespaceref_xml.UserMapper"/>
- 在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>
- 测试
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... //抛出异常,不做处理 } } } }
结果分析
当解析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标签解析完毕,这里另外提一下的是我觉得parsePendingChach
eRefs()这个方法看起来有违和感,中间多了个h,应该写成parsePendingChaceRefs()才觉得对。可能是MyBatis没有注意到的吧。
parsePendingStatements()方法使用场景分析
对于parsePendingStatements的使用场景和上述两个方法是一样,不过,我们还是来举个例子吧。
- 修改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前先解析
- 在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
- 在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注解替代,毋庸置疑不会报错,但是其实现原理是什么呢?有兴趣的同学可以研究一下。