這篇文章將為大家詳細講解有關如何在SpringBoot中使用Mybatis進行緩存,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
緩存的配置
SpringBoot + mybatis 環境搭建很簡單而且網上一堆教程,這里不班門弄斧了,記得在項目中將 mytatis 的源碼下載下來即可。mybaits 一共有兩級緩存:一級緩存的配置 key 是 localCacheScope,而二級緩存的配置 key 是 cacheEnabled,從名字上可以得出以下信息:
一級緩存是本地或者說局部緩存,它不能被關閉,只能配置緩存范圍。SESSION 或者 STATEMENT。
二級緩存才是 mybatis 的正統,功能會更強大些。
先來看下在 SpringBoot中 如何配置 mybatis 緩存的相關信息。默認情況下 SpringBoot 下的 mybatis 一級緩存為 SESSION 級別,二級緩存也是打開的,可以在 mybatis 源碼中的 org.apache.ibatis.session.Configuration.class 文件中看到(idea中打開),如下圖:

也可以通過以下測試程序查看緩存開啟情況:
@RunWith(SpringRunner.class)
@SpringBootTest
public class LearnApplicationTests {
private SqlSessionFactory factory;
@Before
public void setUp() throws Exception {
InputStream inputStream = Resources.getResourceAsStream("mybatis/mybatis-config.xml");
factory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void showDefaultCacheConfiguration() {
System.out.println("一級緩存范圍: " + factory.getConfiguration().getLocalCacheScope());
System.out.println("二級緩存是否被啟用: " + factory.getConfiguration().isCacheEnabled());
}
}如果要設置一級緩存的緩存級別和開關二級緩存,在 mybatis-config.xml (當然也可以在 application.xml/yml 中配置)加入如下配置即可:
<settings> <setting name="cacheEnabled" value="true/false"/> <setting name="localCacheScope" value="SESSION/STATEMENT"/> </settings>
但需要注意的是二級緩存 cacheEnabled 只是個總開關,如果要讓二級緩存真正生效還需要在 mapper xml 文件中加入 。一級緩存只在同一 SESSION 或者 STATEMENT 之間共享,二級緩存可以跨 SESSION,開啟后它們默認具有如下特性:
映射文件中所有的 select 語句將被緩存
映射文件中所有的 insert/update/delete 語句將刷新緩存
一二級緩存同時開啟的情況下,數據的查詢順序是 二級緩存 -> 一級緩存 -> 數據庫。一級緩存比較簡單,而二級緩存可以設置更多的屬性,只需要在 mapper 的 xml 文件中的 中配置即可,具體如下:
<cache type = "org.mybatis.caches.ehcache.LoggingEhcache" //指定使用的緩存類,mybatis默認使用HashMap進行緩存,可以指定第三方緩存 eviction = "LRU" //默認是 LRU 淘汰緩存的算法,有如下幾種: //1.LRU – 最近最少使用的:移除最長時間不被使用的對象。 //2.FIFO – 先進先出:按對象進入緩存的順序來移除它們。 //3.SOFT – 軟引用:移除基于垃圾回收器狀態和軟引用規則的對象。 //4.WEAK – 弱引用:更積極地移除基于垃圾收集器狀態和弱引用規則的對象 flushInterval = "1000" //清空緩存的時間間隔,單位毫秒,可以被設置為任意的正整數。 默認情況是不設置,也就是沒有刷新間隔,緩存僅僅調用語句時刷新。 size = "100" //緩存對象的個數,任意正整數,默認值是1024。 readOnly = "true" //緩存是否只讀,提高讀取效率 blocking = "true" //是否使用阻塞緩存,默認為false,當指定為true時將采用BlockingCache進行封裝,blocking, //阻塞的意思,使用BlockingCache會在查詢緩存時鎖住對應的Key,如果緩存命中了則會釋放對應的鎖, //否則會在查詢數據庫以后再釋放鎖這樣可以阻止并發情況下多個線程同時查詢數據,詳情可參考BlockingCache的源碼。 />
觸發緩存
配置一級緩存為 SESSION 級別
Controller 中調用兩次 getOne,代碼如下:
@RequestMapping("/getUser")
public UserEntity getUser(Long id) {
//第一次調用
UserEntity user1=userMapper.getOne(id);
//第二次調用
UserEntity user2=userMapper.getOne(id);
return user1;
}調用: http://localhost:8080/getUser?id=1,打印結果如下:

從圖中的 1/2/3/4 可以看出每次 mapper 層的一次接口調用如 getOne 就會創建一個 session,并且在執行完畢后關閉 session。所以兩次調用并不在一個 session 中,一級緩存并沒有發生作用。開啟事務,Controller 層代碼如下:
@RequestMapping("/getUser")
@Transactional(rollbackFor = Throwable.class)
public UserEntity getUser(Long id) {
//第一次調用
UserEntity user1=userMapper.getOne(id);
//第二次調用
UserEntity user2=userMapper.getOne(id);
return user1;
}打印結果如下:

由于在同一個事務中,雖然調用了 select 操作兩次但是只執行了一次 sql ,緩存發揮了作用。這就跟一開始我遇到的那個 BUG 場景一樣:同一 session 且 select 調用 > 1 次。如果在兩次調用中間插入 update 操作,緩存會立即失效。只要 session 中有 insert、update 和 delete 語句,該 session 中的緩存會立即被刷新。但是注意這只是在同一 session 之間。不同 session 之間如 session1 和 session2,session1 里的 insert/update/delete 并不會影響 session 2 下的緩存,這在高并發或者分布式的情況下會產生臟數據。所以建議將一級緩存級別調成 statement。
配置一級緩存為 STATEMENT 級別
再次將(1)中的無事務和有事務的代碼分別執行一遍,打印結果始終如下:

配置成 SATEMENT 后,一級緩存相當于被關閉了。STATEMENT 級別暫時不好模擬,但是我猜測 STATEMENT 級別即在同一執行 sql 的接口中(如上面的 getOne 中)緩存,出了 getOne 緩存即失效。
配置二級緩存,同時為了避免一級緩存的干擾,將一級緩存設置為 STATEMENT
Controller 中去掉 @Transactional 注解代碼如下:
@RequestMapping("/getUser")
public UserEntity getUser(Long id) {
UserEntity user1=userMapper.getOne(id);
UserEntity user2=userMapper.getOne(id);
return user1;
}當然二級緩存開關保證打開,在 mapper xml 文件中加入 ,整個文件代碼如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.binggle.learn.dao.mapper.UserMapper" >
<resultMap id="BaseResultMap" type="com.binggle.learn.dao.entity.UserEntity" >
<id column="id" property="id" jdbcType="BIGINT" />
<result column="name" property="name" jdbcType="VARCHAR" />
<result column="sex" property="sex"/>
</resultMap>
<sql id="Base_Column_List" >
id, name, sex
</sql>
<select id="getOne" parameterType="java.lang.Long" resultMap="BaseResultMap" >
SELECT
<include refid="Base_Column_List" />
FROM users
WHERE id = #{id};
</select>
<cache />
</mapper>執行 http://localhost:8080/getUser?id=1,打印結果如下:

從圖中紅框可以看出第二次查詢命中緩存,0.5 是命中率。再次執行 http://localhost:8080/getUser?id=1
打印結果如下:

這次一次 sql 也沒執行了,緩存命中率上升到 0.75了,所以說二級緩存全局緩存。但它的緩存范圍也是有限的,一級緩存在同一個 session 中。二級緩存雖然可以跨 session 但也只能在同一 namespace 中,所謂 namespace 即 mapper xml 文件。具體實驗請看《聊聊 mybatis 的緩存機制》中的關于二級緩存的實驗 4 和 5。再看下二級緩存配置對二級緩存的影響,為了明顯的看出效果,只改如下配置:
<cache size="1" //一次只能緩存一個對象 flushInterval="5000" //刷新時間為 5s />
controller 代碼:
@RequestMapping("/getUser")
public UserEntity getUser(Long id, Long id2) {
//第一個對象 1
System.out.println("================緩存對象 1=================");
UserEntity user1 = userMapper.getOne(id);
//另一個對象 2
System.out.println("========緩存對象 2,剔除緩存中的對象 1=======");
UserEntity user2=userMapper.getOne(id2);
user2 = userMapper.getOne(id2);
//再次讀取第一個對象
System.out.println("==========緩存被剔除,執行查詢 sql===========");
user1 = userMapper.getOne(id);
//暫停 5s
try {
sleep(5000);
}catch (Exception e){
e.printStackTrace();
}
System.out.println("============5s 后再次查詢對象 2=============");
user2 = userMapper.getOne(id2);
return user1;
}執行 http://localhost:8080/getUser?id=1&id2=2 最后打印的結果如下:

太長了,拼接下:

可以看出二級緩存只能緩存一個對象且 5s 后就失效了,配置生效。緩存配置中還有一個重要的配置 type,該配置可以配置第三方的 cache,特別在高并發和分布式情況下。當然,使用更專業的分布式緩存才是王道,例如 redis 等。
總結
本來想總結點什么的,但是覺得推薦文章中總結的非常好,直接引用了:
MyBatis一級緩存的生命周期和SqlSession一致。
MyBatis一級緩存內部設計簡單,只是一個沒有容量限定的HashMap,在緩存的功能性上有所欠缺。
MyBatis的一級緩存最大范圍是SqlSession內部,有多個SqlSession或者分布式的環境下,數據庫寫操作會引起臟數據,建議設定緩存級別為Statement。
MyBatis的二級緩存相對于一級緩存來說,實現了SqlSession之間緩存數據的共享,同時粒度更加的細,能夠到namespace級別,通過Cache接口實現類不同的組合,對Cache的可控性也更強。
MyBatis在多表查詢時,極大可能會出現臟數據,有設計上的缺陷,安全使用二級緩存的條件比較苛刻。
在分布式環境下,由于默認的MyBatis Cache實現都是基于本地的,分布式環境下必然會出現讀取到臟數據,需要使用集中式緩存將MyBatis的Cache接口實現,有一定的開發成本,直接使用Redis、Memcached等分布式緩存可能成本更低,安全性也更高。
個人建議MyBatis緩存特性在生產環境中進行關閉,單純作為一個ORM框架使用可能更為合適。
關于如何在SpringBoot中使用Mybatis進行緩存就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。