项目背景:mybatis3.4.1,mapper接口上没有任何注释,有两个对应的XXXMapper.xml和YYYMapper.xml文件,在xml文件中分别配置
。
首先看下官方文档:
对某一命名空间的语句,只会使用该命名空间的缓存进行缓存或刷新。 但你可能会想要在多个命名空间中共享相同的缓存配置和实例。要实现这种需求,你可以使用
元素来引用另一个缓存。
那如果在同一个xxxMapper.xml文件同时配置
呢?这时当前命名空间是独享缓存还是与引用的缓存共享?
这里先说结论:独享缓存还是共享缓存要根据mybatis解析XXXMapper.xml和YYYMapper.xml文件的次序!
接口有一些crud方法,没有任何注解
public interface DepartmentMapper {
//...
}
public interface EmployeeMapper {
//...
}
DepartmentMapper.xml文件缓存配置如下:
<mapper namespace="com.mybatis.dao.DepartmentMapper">
<cache-ref namespace="com.mybatis.dao.EmployeeMapper"/>
<cache/>
//...
mapper>
这里可以看到同同时配置了
。
EmployeeMapper.xml缓存配置如下:
<mapper namespace="com.mybatis.dao.EmployeeMapper">
<cache />cache>
//...
mapper>
mybatis-config.xml中
标签如何配置呢?下面分析。
下图是创建SqlSessionFactory过程,这里主要描述了二级缓存的创建过程。
如上图流程图示,在解析mapper.xml文件时会先尝试解析
结点,然后解析
。
源码 public Cache useCacheRef(String namespace) {
if (namespace == null) {
throw new BuilderException("cache-ref element requires a namespace attribute.");
}
try {
unresolvedCacheRef = true;
Cache cache = configuration.getCache(namespace);
if (cache == null) {
throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
}
currentCache = cache;
unresolvedCacheRef = false;
return cache;
} catch (IllegalArgumentException e) {
throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
}
}
解释如下:
① 根据namespace尝试从configuration获取对应的cache实例
② 如果获取到,则设置当前MapperBuilderAssistant实例的currentCache为①中获取到的cache实例。否则走③
③ 如果没有获取到,则抛出异常IncompleteElementException 。该异常会被XMLMapperBuilder拦截,将未正常处理的CacheRefResolver放入configuration的Collection
中
private void cacheRefElement(XNode context) {
if (context != null) {
configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
try {
cacheRefResolver.resolveCacheRef();
} catch (IncompleteElementException e) {
configuration.addIncompleteCacheRef(cacheRefResolver);
}
}
}
源码 public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
Cache cache = new CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();
configuration.addCache(cache);
currentCache = cache;
return cache;
}
解释如下
protected final Map caches = new StrictMap<>("Caches collection");
private void parsePendingCacheRefs() {
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...
}
}
}
}
解释如下:
Collection incompleteCacheRefs
MapperBuilderAssistant中解析 源码
的过程OK,有了上面的分析,我们来对比不同
配置时缓存实例具体情况。
也就是说mybatis-config.xml中
标签配置如下:
<mappers>
<mapper resource="com/mybatis/dao/EmployeeMapper.xml"/>
<mapper resource="com/mybatis/dao/DepartmentMapper.xml"/>
mappers>
流程分析如下:
结点
<cache-ref/>
结点,不存在,则无影响Collection incompleteCacheRefs
为空,故而无影响。<cache/>和<cache-ref/>
结点
结点,DepartmentMapper为自己创建一个缓存实例不再引用③中创建的EmployeeMapper的缓存实例;Collection incompleteCacheRefs
为空,故而无影响。结论:DepartmentMapper与EmployeeMapper分别拥有自己的二级缓存Cache实例!
也就是说mybatis-config.xml中
标签配置如下:
<mappers>
<mapper resource="com/mybatis/dao/DepartmentMapper.xml"/>
<mapper resource="com/mybatis/dao/EmployeeMapper.xml"/>
mappers>
流程分析如下:
<cache/>和<cache-ref/>
结点
<cache-ref/>
结点,此时不存在EmployeeMapper的缓存实例,故而抛出异常。将DepartmentMapper对应的CacheRefResolver放入configuration的incompleteCacheRefs集合中Collection incompleteCacheRefs
有一条数据,但是仍旧没有对应的EmployeeMapper的缓存实例。故而不做处理。
结点
<cache-ref/>
结点,不存在,则无影响Collection incompleteCacheRefs
有DepartmentMapper的CacheRefResolver,需要进行处理。会将DepartmentMapper的缓存引用指向EmployeeMapper的缓存实例。结论:EmployeeMapper拥有自己的二级缓存Cache实例,DepartmentMapper的缓存引用指向EmployeeMapper的二级缓存Cache实例!
综上所述,永远不要在一个mapper.xml中同时配置<cache/>和<cache-ref/>
标签(也永远不要在一个mapper接口类上面同时配置@CacheNamespace和@CacheNamespaceRef
注解)