怎么在Springboot2.0通過redis實現(xiàn)支持分布式的mybatis二級緩存

這篇文章主要介紹“怎么在Springboot2.0通過redis實現(xiàn)支持分布式的mybatis二級緩存”,在日常操作中,相信很多人在怎么在Springboot2.0通過redis實現(xiàn)支持分布式的mybatis二級緩存問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”怎么在Springboot2.0通過redis實現(xiàn)支持分布式的mybatis二級緩存”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

我們提供的服務有:成都網(wǎng)站建設、成都網(wǎng)站設計、微信公眾號開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認證、安澤ssl等。為千余家企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務,是有科學管理、有技術的安澤網(wǎng)站制作公司

最近領導要求在項目中加下mybatis二級緩存,由于當前項目是分布式微服務,且是多節(jié)點部署的,而司內(nèi)緩存中間件使用的redis,那很自然的要用redis做分布式緩存支持,避免出現(xiàn)直接使用原生mybatis二級緩存造成緩存數(shù)據(jù)不一致等問題。下面會對基于redis的mybatis二級緩存實現(xiàn)做下簡單介紹,涉及一些概念,同時一些坑點做下整理。

1. 一級緩存

一級緩存是在SqlSession級別的緩存,MyBatis默認開啟一級緩存。即同一個SqlSession對象,相同參數(shù)多次調(diào)用同一個Mapper方法時,只執(zhí)行一次SQL,第一次查詢后數(shù)據(jù)被緩存起來,之后的調(diào)用在沒有緩存刷新、超時情況下都是直接先從緩存中取數(shù)據(jù),不再去查數(shù)據(jù)庫。不同SqlSession間,緩存是隔離的。

怎么在Springboot2.0通過redis實現(xiàn)支持分布式的mybatis二級緩存

此外實際項目開發(fā)中,一級緩存存在很大的局限性,我們的項目一般是Spring+Mybatis集成開發(fā),而Spring的事務管理在邏輯層,每個service對應不同的SqlSession(這是通過MapperScannerConfigurer類創(chuàng)建SqlSession自動注入到service中的), 每次查詢之后都會關閉SqlSession,緩存數(shù)據(jù)就會被清空。所以Spring整合之后,如果沒有事務,一級緩存是沒有實際意義的。

2. 二級緩存

二級緩存是Mapper級別的緩存,Mybatis默認不開啟二級緩存。二級緩存的作用域是mapper的namespace,即相同namespace的兩個mapper將共用同一緩存區(qū)域;支持跨SqlSession,即多個SqlSession可以共享一個mapper緩存。實現(xiàn)上是基于PerpetualCache的HashMap做本地存儲,也支持自定義三方存儲如ehcache、redis、memcache等,用于支持分布式。在本地使用HashMap存儲緩存時,key為hashCode+sqlId+Sql語句(查詢參數(shù)好像也參與,demo用的selectAll,沒怎么關注),其他三方存儲時key也差不多。

怎么在Springboot2.0通過redis實現(xiàn)支持分布式的mybatis二級緩存

注意:開啟二級緩存后

  • 所有在映射文件里的select 語句都將被緩存。

  • 所有在映射文件里insert,update 和delete 語句會清空緩存。

  • 緩存默認使用“最近很少使用”LRU算法來回收

  • 緩存不會被設定的時間所清空。

  • 每個緩存可以存儲1024 個列表或?qū)ο蟮囊茫ú还懿樵兂鰜淼慕Y(jié)果是什么)。

  • 緩存將作為“讀/寫”緩存,意味著獲取的對象不是共享的且對調(diào)用者是安全的。不會有其它的調(diào)用干擾其他調(diào)用者或線程所做的潛在修改

實現(xiàn)步驟:

1、全局cache-enable開關設置,此開關默認為true(實踐證明不設置也行)

  • 創(chuàng)建mybatis-config.xml的配置文件

<?xml version="1.0">
  • Mybatis配置SqlSessionFactory時加載該配置

factory.setConfigLocation(new ClassPathResource("mybatis-config.xml"));

注:通過mybatis-config.xml配置緩存開關,驗證啟停正常;通過配置屬性mybatis.configuration.cache-enabled=true的設置不起作用,原因有待探究

2、mapper.xml中<cache/>緩存標簽的開啟

  • 這是二級緩存開啟的關鍵,如下配置是mybatis本地緩存,作用于整個mapper的所有查詢,若某個<select>不需要緩存,設置useCache=false即可

<cache eviction="FIFO"  flushInterval="60000"  size="512"  readOnly="true"/>
  • 若要自定義三方存儲,需要自實現(xiàn)org.apache.ibatis.cache.Cache接口,并在<cache type="com.bkjk.growth.configs.MybatisRedisCache"/>標簽中指定自定義實現(xiàn)。另外關于Spring的ApplicationContext上下文獲取,簡單提一句,即實現(xiàn)ApplicationContextAware接口即可切入上下文

@Slf4j
public class MybatisRedisCache implements Cache {
    // RedisTemplate實例的封裝工具類
	RedisUtilHandler redisUtilHandler;
    private String id;
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public MybatisRedisCache(final String id) {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        }
        this.id = id;
    }
    
    // 通過Spring上下文獲取redis操作類
    // 個人理解mybatis的某些配置加載是工作在攔截器層,初始化會早于IOC容器的某些bean的加載,這會通過spring自動注入 
    // 是拿不到代理對象的,所以這里做后置延遲處理,調(diào)用時再從上下文的獲取Bean
    private RedisUtilHandler getRedisHandler(){
        if(redisUtilHandler == null){
        	redisUtilHandler = SpringContextHolder.getBean("redisUtilHandler");
        }
        return redisUtilHandler;
    }
    @Override
    public void clear() {
        try {
        	RedisUtilHandler redisUtilHandler = getRedisHandler();
        	redisUtilHandler.flushCache(id);
        } catch (Exception e) {
            log.error("clear Exception: {}", e);
        } 
    }
    @Override
    public String getId() {
        return this.id;
    }
    @Override
    public void putObject(Object key, Object value) {
        try {
        	RedisUtilHandler redisUtilHandler = getRedisHandler();
        	redisUtilHandler.setCache(key.toString(), value, 1, TimeUnit.DAYS);
        } catch (Exception e) {
            log.error("putObject Exception: {}", e);
        } 
    }
    @Override
    public Object getObject(Object key) {
        Object result = null;
        try {
        	RedisUtilHandler redisUtilHandler = getRedisHandler();
        	result = redisUtilHandler.getCache(key.toString(), Object.class);
        } catch (Exception e) {
        	log.error("getObject Exception: key###{} {}", key, e);
        } 
        return result;
        
    }
    @Override
    public Object removeObject(Object key) {
        Object result = null;
        try {
        	RedisUtilHandler redisUtilHandler = getRedisHandler();
        	redisUtilHandler.delete(key.toString());
        } catch (Exception e) {
            log.error("clear Exception: {}", e);
        }
        return result;
    }

    @Override
    public int getSize() {
    	RedisUtilHandler redisUtilHandler = getRedisHandler();
        Long size = (Long) redisUtilHandler.getInstance().execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                return connection.dbSize();
            }
        });
        return size.intValue();

    }
    @Override
    public ReadWriteLock getReadWriteLock() {
        return this.readWriteLock;
    }
}
<mapper namespace="com.xxx.xxx.xxx.repository.mapper.UserMapper">
<cache type="com.xxx.xxx.MybatisRedisCache"/>
  <resultMap id="BaseResultMap" type="com.xxx.xxx.xxx.repository.model.User">
    <id column="id" jdbcType="INTEGER" property="id" />
    <result column="union_id" jdbcType="VARCHAR" property="unionId" />
    <result column="user_id" jdbcType="VARCHAR" property="userId" />
    <result column="user_name" jdbcType="VARCHAR" property="userName" />
    ...
  </resultMap>
  <select id="selectUsers" resultMap="BaseResultMap">
  	SELECT * FROM table
  </select>
  <select id="selectUserById" resultMap="BaseResultMap" useCache="false">
  	SELECT * FROM table WHERE user_id = #{userId}
  </select>

3、Model實體類需要做序列化

public class User implements Serializable{
   private static final long serialVersionUID = -6596381461353742505L;
   ...

}

本文是以redis作為存儲介質(zhì),在redis配置時即指定了key、value的序列化方式,所以我在這步時實體類上序列化可有可無(也有人說即使本地緩存也不需要)

執(zhí)行示例結(jié)果:

怎么在Springboot2.0通過redis實現(xiàn)支持分布式的mybatis二級緩存

坑點整理:

在第二步實現(xiàn)時,我用了<cache/>標簽來開啟二級緩存,此處還可以在mybatis的mapper接口類上使用等效注解來開啟二級緩存,注解如下:

@CacheNamespace(implementation = com.xxx.xxx.configs.MybatisRedisCache.class)

但使用注解和xml中<cache/>標簽不能同時作用,也就是說使用注解時,只能在Mapper接口的方法上用@Select注解綁定執(zhí)行SQL,緩存才有效;同樣使用<cache/>標簽,則只能在mapper.xml中定義<select>標簽進行帶緩存查詢。兩者同時存在也只會有一種起效,是哪種可以自己試試。

缺陷分析:

  • Mybatis自身的緩存天生不支持分布式,需要整合其他第三方緩存庫

        好在本文既是以redis作為自定義緩存來實現(xiàn)的,可以解決這個問題

  • 由于二級緩存是基于mapper級別的,以命名空間(namespace)隔離,可能導致聯(lián)表查詢的數(shù)據(jù)臟讀

        這樣的情況會發(fā)生在做聯(lián)表查詢時,參與聯(lián)合查詢的表在被其中一個或者多個namespace做數(shù)據(jù)緩存時,都是存的彼此初次關聯(lián)查詢時的數(shù)據(jù)鏡像,而這之后各個namespace下表數(shù)據(jù)的更新了,二級緩存是不知道的,也就造成了數(shù)據(jù)臟讀。 

        能想到的處理方式:

        1、聯(lián)表查詢,關聯(lián)的所有表的操作都必須在同一個namespace。這個很難保證

        2、縮小緩存有效時間,當前是基于redis的三方緩存,可以自行設定失效時間,應當在不影響業(yè)務性能的情況下盡量縮短緩存有效時間。但問題其實同樣是治標不治本

至此,mybatis二級緩存應該是比較全的使用實現(xiàn)了?;谌毕萆线€有一點思考,我們的項目是否真的需要用到mybatis的二級緩存?像其他使用者說的,mybatis可是默認關閉二級緩存的,所以由此你該多考慮一下;如果是某些必要場景,比如訪問頻次較高的大單表查詢,或者是表數(shù)據(jù)更新頻次不會太高,緩存時效可以覆蓋變更頻率的,二級緩存還是不錯的選擇。其他復雜場景的緩存建議還是自己做業(yè)務緩存或者直接上Spring Cache比較劃得來。

附:mapper中配置的參數(shù)說明:

  • eviction(可用的收回策略)默認為 LRU

    • LRU – 最近最少使用的:移除最長時間不被使用的對象。

    • FIFO – 先進先出:按對象進入緩存的順序來移除它們。

    • SOFT – 軟引用:移除基于垃圾回收器狀態(tài)和軟引用規(guī)則的對象。

    • WEAK – 弱引用:更積極地移除基于垃圾收集器狀態(tài)和弱引用規(guī)則的對象。

  • flushInterval(刷新間隔)可以被設置為任意的正整數(shù),而且它們代表一個合理的毫秒形式的時間段。默認情況是不設置,也就是沒有刷新間隔,緩存僅僅調(diào)用語句時刷新。

  • size(引用數(shù)目)可以被設置為任意正整數(shù),要記住你緩存的對象數(shù)目和你運行環(huán)境的可用內(nèi)存資源數(shù)目。默認值1024。

  • readOnly(只讀)屬性可以被設置為 true 或 false。只讀的緩存會給所有調(diào)用者返回緩存對象的相同實例。因此這些對象不能被修改。這提供了很重要的性能優(yōu)勢??勺x寫的緩存會返回緩存對象的拷貝(通過序列化)。這會慢一些,但是安全,因此默認是false。

到此,關于“怎么在Springboot2.0通過redis實現(xiàn)支持分布式的mybatis二級緩存”的學習就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續(xù)學習更多相關知識,請繼續(xù)關注創(chuàng)新互聯(lián)網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

網(wǎng)頁題目:怎么在Springboot2.0通過redis實現(xiàn)支持分布式的mybatis二級緩存
文章轉(zhuǎn)載:http://muchs.cn/article16/ihjogg.html

成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供定制網(wǎng)站、網(wǎng)站設計公司、網(wǎng)站策劃、小程序開發(fā)、定制開發(fā)服務器托管

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

外貿(mào)網(wǎng)站制作