您好,登錄后才能下訂單哦!
這篇文章主要介紹“怎么在Springboot2.0通過(guò)redis實(shí)現(xiàn)支持分布式的mybatis二級(jí)緩存”,在日常操作中,相信很多人在怎么在Springboot2.0通過(guò)redis實(shí)現(xiàn)支持分布式的mybatis二級(jí)緩存問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”怎么在Springboot2.0通過(guò)redis實(shí)現(xiàn)支持分布式的mybatis二級(jí)緩存”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!
最近領(lǐng)導(dǎo)要求在項(xiàng)目中加下mybatis二級(jí)緩存,由于當(dāng)前項(xiàng)目是分布式微服務(wù),且是多節(jié)點(diǎn)部署的,而司內(nèi)緩存中間件使用的redis,那很自然的要用redis做分布式緩存支持,避免出現(xiàn)直接使用原生mybatis二級(jí)緩存造成緩存數(shù)據(jù)不一致等問(wèn)題。下面會(huì)對(duì)基于redis的mybatis二級(jí)緩存實(shí)現(xiàn)做下簡(jiǎn)單介紹,涉及一些概念,同時(shí)一些坑點(diǎn)做下整理。
1. 一級(jí)緩存
一級(jí)緩存是在SqlSession級(jí)別的緩存,MyBatis默認(rèn)開(kāi)啟一級(jí)緩存。即同一個(gè)SqlSession對(duì)象,相同參數(shù)多次調(diào)用同一個(gè)Mapper方法時(shí),只執(zhí)行一次SQL,第一次查詢后數(shù)據(jù)被緩存起來(lái),之后的調(diào)用在沒(méi)有緩存刷新、超時(shí)情況下都是直接先從緩存中取數(shù)據(jù),不再去查數(shù)據(jù)庫(kù)。不同SqlSession間,緩存是隔離的。
此外實(shí)際項(xiàng)目開(kāi)發(fā)中,一級(jí)緩存存在很大的局限性,我們的項(xiàng)目一般是Spring+Mybatis集成開(kāi)發(fā),而Spring的事務(wù)管理在邏輯層,每個(gè)service對(duì)應(yīng)不同的SqlSession(這是通過(guò)MapperScannerConfigurer類(lèi)創(chuàng)建SqlSession自動(dòng)注入到service中的), 每次查詢之后都會(huì)關(guān)閉SqlSession,緩存數(shù)據(jù)就會(huì)被清空。所以Spring整合之后,如果沒(méi)有事務(wù),一級(jí)緩存是沒(méi)有實(shí)際意義的。
2. 二級(jí)緩存
二級(jí)緩存是Mapper級(jí)別的緩存,Mybatis默認(rèn)不開(kāi)啟二級(jí)緩存。二級(jí)緩存的作用域是mapper的namespace,即相同namespace的兩個(gè)mapper將共用同一緩存區(qū)域;支持跨SqlSession,即多個(gè)SqlSession可以共享一個(gè)mapper緩存。實(shí)現(xiàn)上是基于PerpetualCache的HashMap做本地存儲(chǔ),也支持自定義三方存儲(chǔ)如ehcache、redis、memcache等,用于支持分布式。在本地使用HashMap存儲(chǔ)緩存時(shí),key為hashCode+sqlId+Sql語(yǔ)句(查詢參數(shù)好像也參與,demo用的selectAll,沒(méi)怎么關(guān)注),其他三方存儲(chǔ)時(shí)key也差不多。
注意:開(kāi)啟二級(jí)緩存后
所有在映射文件里的select 語(yǔ)句都將被緩存。
所有在映射文件里insert,update 和delete 語(yǔ)句會(huì)清空緩存。
緩存默認(rèn)使用“最近很少使用”LRU算法來(lái)回收
緩存不會(huì)被設(shè)定的時(shí)間所清空。
每個(gè)緩存可以存儲(chǔ)1024 個(gè)列表或?qū)ο蟮囊茫ú还懿樵兂鰜?lái)的結(jié)果是什么)。
緩存將作為“讀/寫(xiě)”緩存,意味著獲取的對(duì)象不是共享的且對(duì)調(diào)用者是安全的。不會(huì)有其它的調(diào)用干擾其他調(diào)用者或線程所做的潛在修改
實(shí)現(xiàn)步驟:
1、全局cache-enable開(kāi)關(guān)設(shè)置,此開(kāi)關(guān)默認(rèn)為true(實(shí)踐證明不設(shè)置也行)
創(chuàng)建mybatis-config.xml的配置文件
<?xml version="1.0">
Mybatis配置SqlSessionFactory時(shí)加載該配置
factory.setConfigLocation(new ClassPathResource("mybatis-config.xml"));
注:通過(guò)mybatis-config.xml配置緩存開(kāi)關(guān),驗(yàn)證啟停正常;通過(guò)配置屬性mybatis.configuration.cache-enabled=true的設(shè)置不起作用,原因有待探究
2、mapper.xml中<cache/>緩存標(biāo)簽的開(kāi)啟
這是二級(jí)緩存開(kāi)啟的關(guān)鍵,如下配置是mybatis本地緩存,作用于整個(gè)mapper的所有查詢,若某個(gè)<select>不需要緩存,設(shè)置useCache=false即可
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
若要自定義三方存儲(chǔ),需要自實(shí)現(xiàn)org.apache.ibatis.cache.Cache接口,并在<cache type="com.bkjk.growth.configs.MybatisRedisCache"/>標(biāo)簽中指定自定義實(shí)現(xiàn)。另外關(guān)于Spring的ApplicationContext上下文獲取,簡(jiǎn)單提一句,即實(shí)現(xiàn)ApplicationContextAware接口即可切入上下文
@Slf4j public class MybatisRedisCache implements Cache { // RedisTemplate實(shí)例的封裝工具類(lèi) 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; } // 通過(guò)Spring上下文獲取redis操作類(lèi) // 個(gè)人理解mybatis的某些配置加載是工作在攔截器層,初始化會(huì)早于IOC容器的某些bean的加載,這會(huì)通過(guò)spring自動(dòng)注入 // 是拿不到代理對(duì)象的,所以這里做后置延遲處理,調(diào)用時(shí)再?gòu)纳舷挛牡墨@取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實(shí)體類(lèi)需要做序列化
public class User implements Serializable{ private static final long serialVersionUID = -6596381461353742505L; ... }
本文是以redis作為存儲(chǔ)介質(zhì),在redis配置時(shí)即指定了key、value的序列化方式,所以我在這步時(shí)實(shí)體類(lèi)上序列化可有可無(wú)(也有人說(shuō)即使本地緩存也不需要)
執(zhí)行示例結(jié)果:
坑點(diǎn)整理:
在第二步實(shí)現(xiàn)時(shí),我用了<cache/>標(biāo)簽來(lái)開(kāi)啟二級(jí)緩存,此處還可以在mybatis的mapper接口類(lèi)上使用等效注解來(lái)開(kāi)啟二級(jí)緩存,注解如下:
@CacheNamespace(implementation = com.xxx.xxx.configs.MybatisRedisCache.class)
但使用注解和xml中<cache/>標(biāo)簽不能同時(shí)作用,也就是說(shuō)使用注解時(shí),只能在Mapper接口的方法上用@Select注解綁定執(zhí)行SQL,緩存才有效;同樣使用<cache/>標(biāo)簽,則只能在mapper.xml中定義<select>標(biāo)簽進(jìn)行帶緩存查詢。兩者同時(shí)存在也只會(huì)有一種起效,是哪種可以自己試試。
缺陷分析:
Mybatis自身的緩存天生不支持分布式,需要整合其他第三方緩存庫(kù)
好在本文既是以redis作為自定義緩存來(lái)實(shí)現(xiàn)的,可以解決這個(gè)問(wèn)題
由于二級(jí)緩存是基于mapper級(jí)別的,以命名空間(namespace)隔離,可能導(dǎo)致聯(lián)表查詢的數(shù)據(jù)臟讀
這樣的情況會(huì)發(fā)生在做聯(lián)表查詢時(shí),參與聯(lián)合查詢的表在被其中一個(gè)或者多個(gè)namespace做數(shù)據(jù)緩存時(shí),都是存的彼此初次關(guān)聯(lián)查詢時(shí)的數(shù)據(jù)鏡像,而這之后各個(gè)namespace下表數(shù)據(jù)的更新了,二級(jí)緩存是不知道的,也就造成了數(shù)據(jù)臟讀。
能想到的處理方式:
1、聯(lián)表查詢,關(guān)聯(lián)的所有表的操作都必須在同一個(gè)namespace。這個(gè)很難保證
2、縮小緩存有效時(shí)間,當(dāng)前是基于redis的三方緩存,可以自行設(shè)定失效時(shí)間,應(yīng)當(dāng)在不影響業(yè)務(wù)性能的情況下盡量縮短緩存有效時(shí)間。但問(wèn)題其實(shí)同樣是治標(biāo)不治本
至此,mybatis二級(jí)緩存應(yīng)該是比較全的使用實(shí)現(xiàn)了?;谌毕萆线€有一點(diǎn)思考,我們的項(xiàng)目是否真的需要用到mybatis的二級(jí)緩存?像其他使用者說(shuō)的,mybatis可是默認(rèn)關(guān)閉二級(jí)緩存的,所以由此你該多考慮一下;如果是某些必要場(chǎng)景,比如訪問(wèn)頻次較高的大單表查詢,或者是表數(shù)據(jù)更新頻次不會(huì)太高,緩存時(shí)效可以覆蓋變更頻率的,二級(jí)緩存還是不錯(cuò)的選擇。其他復(fù)雜場(chǎng)景的緩存建議還是自己做業(yè)務(wù)緩存或者直接上Spring Cache比較劃得來(lái)。
附:mapper中配置的參數(shù)說(shuō)明:
eviction
(可用的收回策略)默認(rèn)為 LRU
LRU
– 最近最少使用的:移除最長(zhǎng)時(shí)間不被使用的對(duì)象。
FIFO
– 先進(jìn)先出:按對(duì)象進(jìn)入緩存的順序來(lái)移除它們。
SOFT
– 軟引用:移除基于垃圾回收器狀態(tài)和軟引用規(guī)則的對(duì)象。
WEAK
– 弱引用:更積極地移除基于垃圾收集器狀態(tài)和弱引用規(guī)則的對(duì)象。
flushInterval
(刷新間隔)可以被設(shè)置為任意的正整數(shù),而且它們代表一個(gè)合理的毫秒形式的時(shí)間段。默認(rèn)情況是不設(shè)置,也就是沒(méi)有刷新間隔,緩存僅僅調(diào)用語(yǔ)句時(shí)刷新。
size
(引用數(shù)目)可以被設(shè)置為任意正整數(shù),要記住你緩存的對(duì)象數(shù)目和你運(yùn)行環(huán)境的可用內(nèi)存資源數(shù)目。默認(rèn)值1024。
readOnly
(只讀)屬性可以被設(shè)置為 true 或 false。只讀的緩存會(huì)給所有調(diào)用者返回緩存對(duì)象的相同實(shí)例。因此這些對(duì)象不能被修改。這提供了很重要的性能優(yōu)勢(shì)。可讀寫(xiě)的緩存會(huì)返回緩存對(duì)象的拷貝(通過(guò)序列化)。這會(huì)慢一些,但是安全,因此默認(rèn)是false。
到此,關(guān)于“怎么在Springboot2.0通過(guò)redis實(shí)現(xiàn)支持分布式的mybatis二級(jí)緩存”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。