MyBatis二級緩存實現(xiàn)關聯(lián)刷新
1.MyBatis緩存介紹
Mybatis提供對緩存的支持,但是在沒有配置的默認情況下,它只開啟一級緩存,二級緩存需要手動開啟。
一級緩存只是相對于同一個SqlSession而言。 也就是針對于同一事務,多次執(zhí)行同一Mapper的相同查詢方法,第一查詢后,MyBatis會將查詢結果放入緩存,在中間不涉及相應Mapper的數(shù)據(jù)更新(Insert,Update和Delete)操作的情況下,后續(xù)的查詢將會從緩存中獲取,而不會查詢數(shù)據(jù)庫。
二級緩存是針對于應用級別的緩存,也就是針對不同的SqlSession做到緩存。 當開啟二級緩存時,MyBatis會將首次查詢結果存入對于Mapper的全局緩存,如果中間不執(zhí)行該Mapper的數(shù)據(jù)更新操作,那么后續(xù)的相同查詢都將會從緩存中獲取。
2.二級緩存問題
根據(jù)二級緩存的介紹發(fā)現(xiàn),如果Mapper只是單表查詢,并不會出現(xiàn)問題,但是如果Mapper涉及的查詢出現(xiàn) 聯(lián)表 查詢,如 UserMapper 在查詢 user 信息時需要關聯(lián)查詢 組織信息,也就是需要 user 表和 organization 表關聯(lián),OrganizationMapper 在執(zhí)行更新時并不會更新 UserMapper 的緩存,結果會導致在使用相同條件 使用 UserMapper 查詢 user 信息時,會等到未更新前的 organization 信息,造成數(shù)據(jù)不一致的情況。
2.1 數(shù)據(jù)不一致問題驗證
查詢SQL
SELECT
u.*, o.name org_name
FROM
user u
LEFT JOIN organization o ON u.org_id = o.id
WHERE
u.id = #{userId}
UserMapper
UserInfo queryUserInfo(@Param("userId") String userId);
UserService
public UserEntity queryUser(String userId) {
? ? UserInfo userInfo = userMapper.queryUserInfo(userId);
? ? return userInfo;
}調(diào)用查詢,得到查詢結果(多次查詢,得到緩存數(shù)據(jù)),這里 userId = 1,data為user查詢結果
{
"code": "1",
"message": null,
"data": {
"id": "1",
"username": "admin",
"password": "admin",
"orgName": "組織1"
}
}
查詢 對應 organization 信息,結果
"code": "1",
"message": null,
"data": {
"id": "1",
"name": "組織1"
}
}
執(zhí)行更新 organization 操作,將 組織1 改為 組織2,再次查詢組織信息
"code": "1",
"message": null,
"data": {
"id": "1",
"name": "組織2"
}
}
再次查詢user信息,發(fā)現(xiàn)依舊從緩存中獲取
"code": "1",
"message": null,
"data": {
"id": "1",
"username": "admin",
"password": "admin",
"orgName": "組織1"
}
}
造成此問題原因為 organization 數(shù)據(jù)信息更新只會自己Mapper對應的緩存數(shù)據(jù),而不會通知到關聯(lián)表organization 的一些Mapper更新對應的緩存數(shù)據(jù)。
2.2 問題處理思路
在 Mapper1 定義時,手動配置 相應的關聯(lián) Mapper2
在 Mapper1 緩存 cache1 實例化時,讀取 所關聯(lián)的 Mapper2 的緩存 cache2相關信息
在 cache1 中存儲 cache2 的引用信息
cache1 執(zhí)行clear時,同步操作 cache2 執(zhí)行clear
3.關聯(lián)緩存刷新實現(xiàn)
打開二級緩存,本地項目使用 MyBatis Plus
mybatis-plus.configuration.cache-enabled=true
主要用到自定義注解CacheRelations,自定義緩存實現(xiàn)RelativeCache和緩存上下文RelativeCacheContext。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheRelations {
// from中mapper class對應的緩存更新時,需要更新當前注解標注mapper的緩存
Class<?>[] from() default {};
// 當前注解標注mapper的緩存更新時,需要更新to中mapper class對應的緩存
Class<?>[] to() default {};
}
RelativeCache實現(xiàn) MyBatis Cache 接口
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 {
? ? ? ? ? ? // 判斷 當前緩存是否正在清空,如果正在清空,取消本次操作
? ? ? ? ? ? // 避免緩存出現(xiàn) 循環(huán) relation,造成遞歸無終止,調(diào)用棧溢出
? ? ? ? ? ? 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);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? }
}緩存上下文RelativeCacheContext
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);
? ? }
}UserMapper:
@Repository
@CacheNamespace(implementation = RelativeCache.class, eviction = RelativeCache.class, flushInterval = 30 * 60 * 1000)
@CacheRelations(from = OrganizationMapper.class)
public interface UserMapper extends BaseMapper<UserEntity> {
? ? UserInfo queryUserInfo(@Param("userId") String userId);
}queryUserInfo是xml實現(xiàn)的接口,所以需要在對應xml中配置,不然查詢結果不會被緩存化。如果接口為 BaseMapper實現(xiàn),查詢結果會自動緩存化。
UserMapper.xml
<mapper namespace="com.mars.system.dao.UserMapper">
<cache-ref namespace="com.mars.system.dao.UserMapper"/>
<select id="queryUserInfo" resultType="com.mars.system.model.UserInfo">
select u.*, o.name org_name from user u left join organization o on u.org_id = o.id
where u.id = #{userId}
</select>
</mapper>
OrganizationMapper.java
@Repository
@CacheNamespace(implementation = RelativeCache.class, eviction = RelativeCache.class, flushInterval = 30 * 60 * 1000)
public interface OrganizationMapper extends BaseMapper<OrganizationEntity> {
}
CacheNamespace中flushInterval 在默認情況下是無效的,也就是說緩存并不會定時清理。ScheduledCache是對flushInterval 功能的實現(xiàn),MyBatis 的緩存體系是用裝飾器進行功能擴展的,所以,如果需要定時刷新,需要使用ScheduledCache給到 RelativeCache添加裝飾。
4.驗證
查詢 userId=1的用戶信息
"code":"1",
"message":null,
"data":{
"id":"1",
"username":"admin",
"password":"admin",
"orgName":"組織1"
}
}
更新組織信息,將 組織1 改為 組織2
"code":"1",
"message":null,
"data":{
"id":"1",
"name":"組織2"
}
}
再次查詢用戶信息
"code":"1",
"message":null,
"data":{
"id":"1",
"username":"admin",
"password":"admin",
"orgName":"組織2"
}
}
到此這篇關于MyBatis二級緩存實現(xiàn)關聯(lián)刷新的文章就介紹到這了,更多相關MyBatis 關聯(lián)刷新內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java經(jīng)典設計模式之模板方法模式定義與用法示例
這篇文章主要介紹了Java經(jīng)典設計模式之模板方法模式,簡單說明了模板方法模式的原理、定義,并結合實例形式分析了java模板方法模式的具體使用方法,需要的朋友可以參考下2017-08-08
Spring Boot @Async 異步任務執(zhí)行方法
本篇文章主要介紹了Spring Boot @Async 異步任務執(zhí)行方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-05-05

