<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          MyBatis 二級緩存 關(guān)聯(lián)刷新實(shí)現(xiàn)

          共 6424字,需瀏覽 13分鐘

           ·

          2022-02-20 16:14


          1

          MyBatis緩存介紹


          Mybatis提供對緩存的支持,但是在沒有配置的默認(rèn)情況下,它只開啟一級緩存,二級緩存需要手動開啟。


          一級緩存只是相對于同一個SqlSession而言。 也就是針對于同一事務(wù),多次執(zhí)行同一Mapper的相同查詢方法,第一查詢后,MyBatis會將查詢結(jié)果放入緩存,在中間不涉及相應(yīng)Mapper的數(shù)據(jù)更新(Insert,Update和Delete)操作的情況下,后續(xù)的查詢將會從緩存中獲取,而不會查詢數(shù)據(jù)庫。


          二級緩存是針對于應(yīng)用級別的緩存,也就是針對不同的SqlSession做到緩存。 當(dāng)開啟二級緩存時,MyBatis會將首次查詢結(jié)果存入對于Mapper的全局緩存,如果中間不執(zhí)行該Mapper的數(shù)據(jù)更新操作,那么后續(xù)的相同查詢都將會從緩存中獲取。


          ?

          2

          二級緩存問題


          根據(jù)二級緩存的介紹發(fā)現(xiàn),如果Mapper只是單表查詢,并不會出現(xiàn)問題,但是如果Mapper涉及的查詢出現(xiàn) 聯(lián)表 查詢,如 UserMapper 在查詢 user 信息時需要關(guān)聯(lián)查詢 組織信息,也就是需要 user 表和 organization 表關(guān)聯(lián),OrganizationMapper 在執(zhí)行更新時并不會更新 UserMapper 的緩存,結(jié)果會導(dǎo)致在使用相同條件 使用 UserMapper 查詢 user 信息時,會等到未更新前的 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"
          ?}
          }


          查詢 對應(yīng) organization 信息,結(jié)果


          {
          ?"code": "1",
          ?"message": null,
          ?"data": {
          ???"id": "1",
          ???"name": "組織1"
          ?}
          }


          發(fā)現(xiàn)和user緩存數(shù)據(jù)一致。


          執(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ù)信息更新只會自己Mapper對應(yīng)的緩存數(shù)據(jù),而不會通知到關(guān)聯(lián)表organization 的一些Mapper更新對應(yīng)的緩存數(shù)據(jù)。


          2.2、問題處理思路


          • 在 Mapper1 定義時,手動配置 相應(yīng)的關(guān)聯(lián) Mapper2

          • 在 Mapper1 緩存 cache1 實(shí)例化時,讀取 所關(guān)聯(lián)的 Mapper2 的緩存 cache2相關(guān)信息

          • 在 cache1 中存儲 cache2 的引用信息

          • cache1 執(zhí)行clear時,同步操作 cache2 執(zhí)行clear


          ?

          3

          關(guān)聯(lián)緩存刷新實(shí)現(xiàn)


          打開二級緩存,本地項(xiàng)目使用 MyBatis Plus


          mybatis-plus.configuration.cache-enabled=true


          主要用到自定義注解CacheRelations,自定義緩存實(shí)現(xiàn)RelativeCache和緩存上下文RelativeCacheContext。


          注解CacheRelations,使用時需標(biāo)注在對應(yīng)mapper上


          @Target(ElementType.TYPE)
          @Retention(RetentionPolicy.RUNTIME)
          public @interface CacheRelations {
          ????//?from中mapper class對應(yīng)的緩存更新時,需要更新當(dāng)前注解標(biāo)注mapper的緩存
          ????Class[] from() default?{};
          ????//?當(dāng)前注解標(biāo)注mapper的緩存更新時,需要更新to中mapper class對應(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 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() {
          ????????// 加載 其他緩存更新時 需要更新此緩存的 caches
          ????????// 將 此緩存 加入至這些 caches 的 relations 中
          ????????List to = UN_LOAD_TO_RELATIVE_CACHES_MAP.get(mapperClass);
          ????????if?(to != null) {
          ????????????to.forEach(relativeCache -> this.addRelation(relativeCache));
          ????????}
          ????????// 加載 此緩存更新時 需要更新的一些緩存 caches
          ????????// 將這些緩存 caches 加入 至 此緩存 relations 中
          ????????List 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還未加載,這時需將對應(yīng)關(guān)系存放到 UN_LOAD_FROM_RELATIVE_CACHES_MAP
          ????????????????????// 也就是說 c 對應(yīng)的 cache 需要 在 當(dāng)前緩存更新時 進(jìn)行更新
          ????????????????????List relativeCaches = UN_LOAD_FROM_RELATIVE_CACHES_MAP.putIfAbsent(c, new?ArrayList());
          ????????????????????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還未加載,這時需將對應(yīng)關(guān)系存放到 UN_LOAD_TO_RELATIVE_CACHES_MAP
          ????????????????????// 也就是說 c 對應(yīng)的 cache 更新時需要更新當(dāng)前緩存
          ????????????????????List relativeCaches = UN_LOAD_TO_RELATIVE_CACHES_MAP.putIfAbsent(c, new?ArrayList());
          ????????????????????relativeCaches.add(this);
          ????????????????}
          ????????????}
          ????????}
          ????}
          }


          緩存上下文RelativeCacheContext


          public?class?RelativeCacheContext?{

          ????// 存儲全量緩存的映射關(guān)系
          ????public?static?final?Map, RelativeCache> MAPPER_CACHE_MAP = new?ConcurrentHashMap<>();
          ????// 存儲 Mapper 對應(yīng)緩存 需要to更新緩存,但是此時 Mapper 對應(yīng)緩存還未加載
          ????// 也就是 Class 對應(yīng)的緩存更新時,需要更新 List 中的緩存
          ????public?static?final?Map, List> UN_LOAD_TO_RELATIVE_CACHES_MAP = new?ConcurrentHashMap<>();
          ????// 存儲 Mapper 對應(yīng)緩存 需要from更新緩存,但是在 加載 Mapper 緩存時,這些緩存還未加載
          ????// 也就是 List 中的緩存更新時,需要更新 Class 對應(yīng)的緩存
          ????public?static?final?Map, List> 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.java


          @Repository
          @CacheNamespace(implementation = RelativeCache.class, eviction = RelativeCache.class, flushInterval = 30?* 60?* 1000)
          @CacheRelations(from = OrganizationMapper.class)
          public interface UserMapper extends BaseMapper {
          ????UserInfo?queryUserInfo(@Param("userId") String userId);
          }


          queryUserInfo是xml實(shí)現(xiàn)的接口,所以需要在對應(yīng)xml中配置,不然查詢結(jié)果不會被緩存化。如果接口為 BaseMapper實(shí)現(xiàn),查詢結(jié)果會自動緩存化。


          UserMapper.xml


          namespace="com.mars.system.dao.UserMapper">
          ????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>


          OrganizationMapper.java


          @Repository
          @CacheNamespace(implementation = RelativeCache.class, eviction = RelativeCache.class, flushInterval = 30?* 60?* 1000)
          public interface OrganizationMapper extends BaseMapper {
          }


          CacheNamespace中flushInterval 在默認(rèn)情況下是無效的,也就是說緩存并不會定時清理。ScheduledCache是對flushInterval 功能的實(shí)現(xiàn),MyBatis 的緩存體系是用裝飾器進(jìn)行功能擴(kuò)展的,所以,如果需要定時刷新,需要使用ScheduledCache給到 RelativeCache添加裝飾。


          至此,配置和編碼完成。


          開始驗(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"
          ????}
          }


          符合預(yù)期。


          來源:blog.csdn.net/qq_38245668/article/details/105803298



          往期推薦



          大專畢業(yè)的我,用了5年拿到年薪50W

          京東二面:商品庫存的扣除過程,如何防止超賣?

          一個寶藏開源軟件,跨平臺終端神器 Tabby!

          char和varchar有哪些區(qū)別?varchar最大長度是多少?

          干掉項(xiàng)目中雜亂的 if-else,試試狀態(tài)模式,這才是優(yōu)雅的實(shí)現(xiàn)方式!

          SpringBoot+flowable快速實(shí)現(xiàn)工作流,優(yōu)秀的工作流輪子



          瀏覽 79
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  操逼视频在线 | 18禁免费观看网站 | 蜜乳在线| 国产夫妻自拍在线观看 | 青娱乐亚洲视频 |