91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

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

發布時間:2021-06-22 17:18:38 來源:億速云 閱讀:205 作者:chen 欄目:大數據

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

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

1. 一級緩存

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

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

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

2. 二級緩存

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

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

注意:開啟二級緩存后

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

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

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

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

  • 每個緩存可以存儲1024 個列表或對象的引用(不管查詢出來的結果是什么)。

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

實現步驟:

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

  • 創建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"/>
  • 若要自定義三方存儲,需要自實現org.apache.ibatis.cache.Cache接口,并在<cache type="com.bkjk.growth.configs.MybatisRedisCache"/>標簽中指定自定義實現。另外關于Spring的ApplicationContext上下文獲取,簡單提一句,即實現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自動注入 
    // 是拿不到代理對象的,所以這里做后置延遲處理,調用時再從上下文的獲取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作為存儲介質,在redis配置時即指定了key、value的序列化方式,所以我在這步時實體類上序列化可有可無(也有人說即使本地緩存也不需要)

執行示例結果:

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

坑點整理:

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

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

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

缺陷分析:

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

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

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

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

        能想到的處理方式:

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

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

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

附:mapper中配置的參數說明:

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

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

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

    • SOFT – 軟引用:移除基于垃圾回收器狀態和軟引用規則的對象。

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

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

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

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

到此,關于“怎么在Springboot2.0通過redis實現支持分布式的mybatis二級緩存”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

鹤山市| 峨眉山市| 都匀市| 抚宁县| 台前县| 梧州市| 卓尼县| 乌什县| 宿迁市| 达拉特旗| 文安县| 根河市| 共和县| 金川县| 延津县| 南汇区| 龙陵县| 句容市| 南宫市| 东源县| 庄河市| 海南省| 泽州县| 郴州市| 定西市| 丰宁| 进贤县| 河源市| 石泉县| 松桃| 宁晋县| 黎城县| 阿拉善盟| 五大连池市| 江西省| 南漳县| 定南县| 乌拉特前旗| 兰西县| 察雅县| 栾川县|