MyBatis二級(jí)緩存實(shí)現(xiàn)關(guān)聯(lián)刷新
1.MyBatis緩存介紹
Mybatis提供對(duì)緩存的支持,但是在沒有配置的默認(rèn)情況下,它只開啟一級(jí)緩存,二級(jí)緩存需要手動(dòng)開啟。
一級(jí)緩存只是相對(duì)于同一個(gè)SqlSession而言。 也就是針對(duì)于同一事務(wù),多次執(zhí)行同一Mapper的相同查詢方法,第一查詢后,MyBatis會(huì)將查詢結(jié)果放入緩存,在中間不涉及相應(yīng)Mapper的數(shù)據(jù)更新(Insert,Update和Delete)操作的情況下,后續(xù)的查詢將會(huì)從緩存中獲取,而不會(huì)查詢數(shù)據(jù)庫。
二級(jí)緩存是針對(duì)于應(yīng)用級(jí)別的緩存,也就是針對(duì)不同的SqlSession做到緩存。 當(dāng)開啟二級(jí)緩存時(shí),MyBatis會(huì)將首次查詢結(jié)果存入對(duì)于Mapper的全局緩存,如果中間不執(zhí)行該Mapper的數(shù)據(jù)更新操作,那么后續(xù)的相同查詢都將會(huì)從緩存中獲取。
2.二級(jí)緩存問題
根據(jù)二級(jí)緩存的介紹發(fā)現(xiàn),如果Mapper只是單表查詢,并不會(huì)出現(xiàn)問題,但是如果Mapper涉及的查詢出現(xiàn) 聯(lián)表 查詢,如 UserMapper 在查詢 user 信息時(shí)需要關(guān)聯(lián)查詢 組織信息,也就是需要 user 表和 organization 表關(guān)聯(lián),OrganizationMapper 在執(zhí)行更新時(shí)并不會(huì)更新 UserMapper 的緩存,結(jié)果會(huì)導(dǎo)致在使用相同條件 使用 UserMapper 查詢 user 信息時(shí),會(huì)等到未更新前的 organization 信息,造成數(shù)據(jù)不一致的情況。
2.1 數(shù)據(jù)不一致問題驗(yàn)證
查詢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)用查詢,得到查詢結(jié)果(多次查詢,得到緩存數(shù)據(jù)),這里 userId = 1,data為user查詢結(jié)果
{ "code": "1", "message": null, "data": { "id": "1", "username": "admin", "password": "admin", "orgName": "組織1" } }
查詢 對(duì)應(yīng) organization 信息,結(jié)果
"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" } }
造成此問題原因?yàn)?organization 數(shù)據(jù)信息更新只會(huì)自己Mapper對(duì)應(yīng)的緩存數(shù)據(jù),而不會(huì)通知到關(guān)聯(lián)表organization 的一些Mapper更新對(duì)應(yīng)的緩存數(shù)據(jù)。
2.2 問題處理思路
在 Mapper1 定義時(shí),手動(dòng)配置 相應(yīng)的關(guān)聯(lián) Mapper2
在 Mapper1 緩存 cache1 實(shí)例化時(shí),讀取 所關(guān)聯(lián)的 Mapper2 的緩存 cache2相關(guān)信息
在 cache1 中存儲(chǔ) cache2 的引用信息
cache1 執(zhí)行clear時(shí),同步操作 cache2 執(zhí)行clear
3.關(guān)聯(lián)緩存刷新實(shí)現(xiàn)
打開二級(jí)緩存,本地項(xiàng)目使用 MyBatis Plus
mybatis-plus.configuration.cache-enabled=true
主要用到自定義注解CacheRelations,自定義緩存實(shí)現(xiàn)RelativeCache和緩存上下文RelativeCacheContext。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface CacheRelations { // from中mapper class對(duì)應(yīng)的緩存更新時(shí),需要更新當(dāng)前注解標(biāo)注mapper的緩存 Class<?>[] from() default {}; // 當(dāng)前注解標(biāo)注mapper的緩存更新時(shí),需要更新to中mapper class對(duì)應(yīng)的緩存 Class<?>[] to() default {}; }
RelativeCache實(shí)現(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 { ? ? ? ? ? ? // 判斷 當(dāng)前緩存是否正在清空,如果正在清空,取消本次操作 ? ? ? ? ? ? // 避免緩存出現(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() { ? ? ? ? // 加載 其他緩存更新時(shí) 需要更新此緩存的 caches ? ? ? ? // 將 此緩存 加入至這些 caches 的 relations 中 ? ? ? ? List<RelativeCache> to = UN_LOAD_TO_RELATIVE_CACHES_MAP.get(mapperClass); ? ? ? ? if (to != null) { ? ? ? ? ? ? to.forEach(relativeCache -> this.addRelation(relativeCache)); ? ? ? ? } ? ? ? ? // 加載 此緩存更新時(shí) 需要更新的一些緩存 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) { ? ? ? ? ? ? ? ? ? ? // 將找到的緩存添加到當(dāng)前緩存的relations中 ? ? ? ? ? ? ? ? ? ? this.addRelation(relativeCache); ? ? ? ? ? ? ? ? } else { ? ? ? ? ? ? ? ? ? ? // 如果找不到 to cache,證明to cache還未加載,這時(shí)需將對(duì)應(yīng)關(guān)系存放到 UN_LOAD_FROM_RELATIVE_CACHES_MAP ? ? ? ? ? ? ? ? ? ? // 也就是說 c 對(duì)應(yīng)的 cache 需要 在 當(dāng)前緩存更新時(shí) 進(jìn)行更新 ? ? ? ? ? ? ? ? ? ? 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) { ? ? ? ? ? ? ? ? ? ? // 將找到的緩存添加到當(dāng)前緩存的relations中 ? ? ? ? ? ? ? ? ? ? relativeCache.addRelation(this); ? ? ? ? ? ? ? ? } else { ? ? ? ? ? ? ? ? ? ? // 如果找不到 from cache,證明from cache還未加載,這時(shí)需將對(duì)應(yīng)關(guān)系存放到 UN_LOAD_TO_RELATIVE_CACHES_MAP ? ? ? ? ? ? ? ? ? ? // 也就是說 c 對(duì)應(yīng)的 cache 更新時(shí)需要更新當(dāng)前緩存 ? ? ? ? ? ? ? ? ? ? List<RelativeCache> relativeCaches = UN_LOAD_TO_RELATIVE_CACHES_MAP.putIfAbsent(c, new ArrayList<RelativeCache>()); ? ? ? ? ? ? ? ? ? ? relativeCaches.add(this); ? ? ? ? ? ? ? ? } ? ? ? ? ? ? } ? ? ? ? } ? ? } }
緩存上下文RelativeCacheContext
public class RelativeCacheContext { ? ? // 存儲(chǔ)全量緩存的映射關(guān)系 ? ? public static final Map<Class<?>, RelativeCache> MAPPER_CACHE_MAP = new ConcurrentHashMap<>(); ? ? // 存儲(chǔ) Mapper 對(duì)應(yīng)緩存 需要to更新緩存,但是此時(shí) Mapper 對(duì)應(yīng)緩存還未加載 ? ? // 也就是 Class<?> 對(duì)應(yīng)的緩存更新時(shí),需要更新 List<RelativeCache> 中的緩存 ? ? public static final Map<Class<?>, List<RelativeCache>> UN_LOAD_TO_RELATIVE_CACHES_MAP = new ConcurrentHashMap<>(); ? ? // 存儲(chǔ) Mapper 對(duì)應(yīng)緩存 需要from更新緩存,但是在 加載 Mapper 緩存時(shí),這些緩存還未加載 ? ? // 也就是 List<RelativeCache> 中的緩存更新時(shí),需要更新 Class<?> 對(duì)應(yīng)的緩存 ? ? 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實(shí)現(xiàn)的接口,所以需要在對(duì)應(yīng)xml中配置,不然查詢結(jié)果不會(huì)被緩存化。如果接口為 BaseMapper實(shí)現(xiàn),查詢結(jié)果會(huì)自動(dòng)緩存化。
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 在默認(rèn)情況下是無效的,也就是說緩存并不會(huì)定時(shí)清理。ScheduledCache是對(duì)flushInterval 功能的實(shí)現(xiàn),MyBatis 的緩存體系是用裝飾器進(jìn)行功能擴(kuò)展的,所以,如果需要定時(shí)刷新,需要使用ScheduledCache給到 RelativeCache添加裝飾。
4.驗(yàn)證
查詢 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" } }
到此這篇關(guān)于MyBatis二級(jí)緩存實(shí)現(xiàn)關(guān)聯(lián)刷新的文章就介紹到這了,更多相關(guān)MyBatis 關(guān)聯(lián)刷新內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Mybatis多表關(guān)聯(lián)查詢的實(shí)現(xiàn)(DEMO)
- Mybatis 一對(duì)多和多對(duì)一關(guān)聯(lián)查詢問題
- mybatis-plus多表關(guān)聯(lián)查詢功能的實(shí)現(xiàn)
- MyBatis實(shí)踐之動(dòng)態(tài)SQL及關(guān)聯(lián)查詢
- MyBatis 三表外關(guān)聯(lián)查詢的實(shí)現(xiàn)(用戶、角色、權(quán)限)
- mybatis實(shí)現(xiàn)一對(duì)一關(guān)聯(lián)映射實(shí)例代碼
- mybatis 多表關(guān)聯(lián)mapper文件寫法操作
相關(guān)文章
j2ee之AJAX二級(jí)聯(lián)動(dòng)效果
這篇文章主要為大家詳細(xì)介紹了j2ee之AJAX二級(jí)聯(lián)動(dòng)效果的實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08Java經(jīng)典設(shè)計(jì)模式之模板方法模式定義與用法示例
這篇文章主要介紹了Java經(jīng)典設(shè)計(jì)模式之模板方法模式,簡單說明了模板方法模式的原理、定義,并結(jié)合實(shí)例形式分析了java模板方法模式的具體使用方法,需要的朋友可以參考下2017-08-08關(guān)于通過Java連接mysql對(duì)反斜杠”\“轉(zhuǎn)義的測(cè)試詳解
這篇文章主要給大家介紹了關(guān)于通過Java連接mysql對(duì)反斜杠”\“轉(zhuǎn)義的測(cè)試的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),對(duì)大家理解反斜杠”\“轉(zhuǎn)義具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起看看吧。2017-06-06Spring Boot @Async 異步任務(wù)執(zhí)行方法
本篇文章主要介紹了Spring Boot @Async 異步任務(wù)執(zhí)行方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-05-05