解决Mybatis关联查询二级缓存不刷新
Mybatis的缓存机制
Mybatis主要是一级缓存和二级缓存,简单说一级缓存是sqlSession级别的缓存,二级缓存是应用级别的缓存。
打一个比喻,假如你是一个点餐app的开发人员,遇上了一个操蛋的顾客,他啥也不干坐在那个地方就开始刷菜单,一分钟刷几千次,这种情况下如果每次菜单内容都从数据库查询,这很显然是不能接受的,因为菜单数据通常不会发生改变,所以可以将菜单的信息存在缓存中,在菜单没有改变的情况下可以直接从缓存读取数据不需要访问数据库。这就是一级缓存的场景。
那假如遇上了一群操蛋的用户,这些用户动不动就刷新菜单玩,但是他们不属于同一个SqlSession。这时候需要启用的就是二级缓存,二级缓存的作用范围可以理解为Mapper,在Mapper发生update操作之前缓存不会刷新。
关联查询与二级缓存
二级缓存的作用范围可以近似的看作Mapper文件,现在假如有一个查询需要关联A,B两张表,这个Sql语句写在A的Mapper文件中,当AMapper发生update操作的时候这条语句对应的二级缓存可以刷新,但是,如果B表发生了更新操作,可是A表没有发生更新,此时缓存中的数据就失去了时效性。
解决方案
既然Mybatis提供了缓存服务,那么对于缓存的管理肯定有相关的刷新方法,刷新方法就在Cache类当中,每当有用户调用insert或者update方法的时候,Mybatis就会调用相应的缓存中的clear方法刷新缓存。
简单的思路就是我们可以标记当前Mapper发生改变的时候对应刷新的关联Mapper,重新Cache类中的clear方法,当Mapper更新的时候,联通关联的Mapper的缓存一起刷新,
首先我们需要写一个注解,把当前需要“重点监控”的mapper相关联的mapper标记出来。`
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheRelations {
// from中mapper class对应的缓存更新时,需要更新当前注解标注mapper的缓存
Class<?>[] from() default {};
// 当前注解标注mapper的缓存更新时,需要更新to中mapper class对应的缓存
Class<?>[] to() default {};
}
在这之后我们就可以重写Cache类,在clear方法中将关联的Mapper的缓存空间刷新。
public class RelativeCache implements Cache {
private Map<Object, Object> CACHE_MAP = new ConcurrentHashMap<>();
private List<RelativeCache> relations = new ArrayList<>();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
private String id;
private Class<?> mapperClass;
private boolean clearing;
public RelativeCache(String id) throws Exception {
this.id = id;
this.mapperClass = Class.forName(id);
RelativeCacheContext.putCache(mapperClass, this);
loadRelations();
}
@Override
public String getId() {
return id;
}
@Override
public void putObject(Object key, Object value) {
CACHE_MAP.put(key, value);
}
@Override
public Object getObject(Object key) {
return CACHE_MAP.get(key);
}
@Override
public Object removeObject(Object key) {
return CACHE_MAP.remove(key);
}
@Override
public void clear() {
ReadWriteLock readWriteLock = getReadWriteLock();
Lock lock = readWriteLock.writeLock();
lock.lock();
try {
// 判断 当前缓存是否正在清空,如果正在清空,取消本次操作
// 避免缓存出现 循环 relation,造成递归无终止,调用栈溢出
if (clearing) {
return;
}
clearing = true;
try {
CACHE_MAP.clear();
relations.forEach(RelativeCache::clear);
} finally {
clearing = false;
}
} finally {
lock.unlock();
}
}
@Override
public int getSize() {
return CACHE_MAP.size();
}
@Override
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
}
public void addRelation(RelativeCache relation) {
if (relations.contains(relation)){
return;
}
relations.add(relation);
}
void loadRelations() {
// 加载 其他缓存更新时 需要更新此缓存的 caches
// 将 此缓存 加入至这些 caches 的 relations 中
List<RelativeCache> to = UN_LOAD_TO_RELATIVE_CACHES_MAP.get(mapperClass);
if (to != null) {
to.forEach(relativeCache -> this.addRelation(relativeCache));
}
// 加载 此缓存更新时 需要更新的一些缓存 caches
// 将这些缓存 caches 加入 至 此缓存 relations 中
List<RelativeCache> from = UN_LOAD_FROM_RELATIVE_CACHES_MAP.get(mapperClass);
if (from != null) {
from.forEach(relativeCache -> relativeCache.addRelation(this));
}
CacheRelations annotation = AnnotationUtils.findAnnotation(mapperClass, CacheRelations.class);
if (annotation == null) {
return;
}
Class<?>[] toMappers = annotation.to();
Class<?>[] fromMappers = annotation.from();
if (toMappers != null && toMappers.length > 0) {
for (Class c : toMappers) {
RelativeCache relativeCache = MAPPER_CACHE_MAP.get(c);
if (relativeCache != null) {
// 将找到的缓存添加到当前缓存的relations中
this.addRelation(relativeCache);
} else {
// 如果找不到 to cache,证明to cache还未加载,这时需将对应关系存放到 UN_LOAD_FROM_RELATIVE_CACHES_MAP
// 也就是说 c 对应的 cache 需要 在 当前缓存更新时 进行更新
List<RelativeCache> relativeCaches = UN_LOAD_FROM_RELATIVE_CACHES_MAP.putIfAbsent(c, new ArrayList<RelativeCache>());
relativeCaches.add(this);
}
}
}
if (fromMappers != null && fromMappers.length > 0) {
for (Class c : fromMappers) {
RelativeCache relativeCache = MAPPER_CACHE_MAP.get(c);
if (relativeCache != null) {
// 将找到的缓存添加到当前缓存的relations中
relativeCache.addRelation(this);
} else {
// 如果找不到 from cache,证明from cache还未加载,这时需将对应关系存放到 UN_LOAD_TO_RELATIVE_CACHES_MAP
// 也就是说 c 对应的 cache 更新时需要更新当前缓存
List<RelativeCache> relativeCaches = UN_LOAD_TO_RELATIVE_CACHES_MAP.putIfAbsent(c, new ArrayList<RelativeCache>());
relativeCaches.add(this);
}
}
}
}
}
除此之外,在做一个上下文用来管理映射关系
public class RelativeCacheContext {
// 存储全量缓存的映射关系
public static final Map<Class<?>, RelativeCache> MAPPER_CACHE_MAP = new ConcurrentHashMap<>();
// 存储 Mapper 对应缓存 需要to更新缓存,但是此时 Mapper 对应缓存还未加载
// 也就是 Class<?> 对应的缓存更新时,需要更新 List<RelativeCache> 中的缓存
public static final Map<Class<?>, List<RelativeCache>> UN_LOAD_TO_RELATIVE_CACHES_MAP = new ConcurrentHashMap<>();
// 存储 Mapper 对应缓存 需要from更新缓存,但是在 加载 Mapper 缓存时,这些缓存还未加载
// 也就是 List<RelativeCache> 中的缓存更新时,需要更新 Class<?> 对应的缓存
public static final Map<Class<?>, List<RelativeCache>> UN_LOAD_FROM_RELATIVE_CACHES_MAP = new ConcurrentHashMap<>();
public static void putCache(Class<?> clazz, RelativeCache cache) {
MAPPER_CACHE_MAP.put(clazz, cache);
}
public static void getCache(Class<?> clazz) {
MAPPER_CACHE_MAP.get(clazz);
}
}
如此一来用@CacheRelations 注解标记在需要监控的Mapper上,在被标记的Mapper有更新操作的时候,与其相关的from或者to的缓存会一起刷新。