最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot下Mybatis的緩存的實現(xiàn)步驟

 更新時間:2019年04月10日 09:47:00   作者:水目沾  
這篇文章主要介紹了SpringBoot下Mybatis的緩存的實現(xiàn)步驟,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

說起 mybatis,作為 Java 程序員應該是無人不知,它是常用的數(shù)據(jù)庫訪問框架。與 Spring 和 Struts 組成了 Java Web 開發(fā)的三劍客--- SSM。當然隨著 Spring Boot 的發(fā)展,現(xiàn)在越來越多的企業(yè)采用的是 SpringBoot + mybatis 的模式開發(fā),我們公司也不例外。而 mybatis 對于我也僅僅停留在會用而已,沒想過怎么去了解它,更不知道它的緩存機制了,直到那個生死難忘的 BUG。故事的背景比較長,但并不是啰嗦,只是讓讀者知道這個 BUG 觸發(fā)的場景,加深記憶。在遇到類似問題時,可以迅速定位。

先說下故事的前提,為了防止用戶在動態(tài)中輸入特殊字符,用戶的動態(tài)都是編碼后發(fā)到后臺,而后臺在存入到 DB 表之前會解碼以方便在 DB 中查看以及上報到搜索引擎。在查詢用戶動態(tài)的時候先從 DB 表中讀取并在后臺做一次編碼再傳到前端,前端再解碼就可以正常展示了。流程如下圖:

有一天后端預發(fā)環(huán)境發(fā)布完畢后,用戶的動態(tài)頁面有的動態(tài)顯示正常,而有的卻是被編碼過的。看到現(xiàn)象后的第一個反應就是有問題的動態(tài)被編碼了兩次,但是編碼操作只會在 service 層的 findById 中有。理論不會在上層犯這種低級錯誤。話不多說便開始排查新增加的代碼,發(fā)現(xiàn)只要進入了新增加代碼中的某個 if 分支則被編碼了兩次。分支中除了再次調用 findById(必要性不討論),也無其他特殊代碼了。百思不得其解后請教了旁邊的老司機,老司機說可能是 mybatis 緩存。于是看了下我代碼,將編碼的操作從 findById 中移出來后再次發(fā)布到預發(fā),正常了,心想老司機不愧是老司機。本次 BUG 觸發(fā)的有兩個條件需要注意:

  • 整個操作過程都在一個函數(shù)中,而函數(shù)上面加了 @Transactional 的注解(對 mybatis 來說是在同一個 SESSION 中)
  • 一般只會調用 findByIdy 一次,如果進入分支則會調用兩次 (第一次調用后做了編碼后被緩存,第二次從緩存讀后繼續(xù)被編碼)

便開始谷歌 mybatis 的緩存機制,搜到了一篇非常不錯的文章《聊聊 mybatis 的緩存機制 》,推薦大家看一下。但是這篇文章講到了源碼,涉及的比較深。而且并沒講 SpringBoot 下 mybatis 下的緩存知識點,遂作此篇,以作補充。

緩存的配置

SpringBoot + mybatis 環(huán)境搭建很簡單而且網(wǎng)上一堆教程,這里不班門弄斧了,記得在項目中將 mytatis 的源碼下載下來即可。mybaits 一共有兩級緩存:一級緩存的配置 key 是 localCacheScope,而二級緩存的配置 key 是 cacheEnabled,從名字上可以得出以下信息:

  • 一級緩存是本地或者說局部緩存,它不能被關閉,只能配置緩存范圍。SESSION 或者 STATEMENT。
  • 二級緩存才是 mybatis 的正統(tǒng),功能會更強大些。

先來看下在 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 語句將刷新緩存

一二級緩存同時開啟的情況下,數(shù)據(jù)的查詢順序是 二級緩存 -> 一級緩存 -> 數(shù)據(jù)庫。一級緩存比較簡單,而二級緩存可以設置更多的屬性,只需要在 mapper 的 xml 文件中的 中配置即可,具體如下:

<cache
  type = "org.mybatis.caches.ehcache.LoggingEhcache" //指定使用的緩存類,mybatis默認使用HashMap進行緩存,可以指定第三方緩存
  eviction = "LRU" //默認是 LRU 淘汰緩存的算法,有如下幾種:
       //1.LRU – 最近最少使用的:移除最長時間不被使用的對象。 
       //2.FIFO – 先進先出:按對象進入緩存的順序來移除它們。 
       //3.SOFT – 軟引用:移除基于垃圾回收器狀態(tài)和軟引用規(guī)則的對象。 
       //4.WEAK – 弱引用:更積極地移除基于垃圾收集器狀態(tài)和弱引用規(guī)則的對象
  flushInterval = "1000" //清空緩存的時間間隔,單位毫秒,可以被設置為任意的正整數(shù)。 默認情況是不設置,也就是沒有刷新間隔,緩存僅僅調用語句時刷新。
  size = "100"  //緩存對象的個數(shù),任意正整數(shù),默認值是1024。
  readOnly = "true" //緩存是否只讀,提高讀取效率
  blocking = "true" //是否使用阻塞緩存,默認為false,當指定為true時將采用BlockingCache進行封裝,blocking,
       //阻塞的意思,使用BlockingCache會在查詢緩存時鎖住對應的Key,如果緩存命中了則會釋放對應的鎖,
       //否則會在查詢數(shù)據(jù)庫以后再釋放鎖這樣可以阻止并發(fā)情況下多個線程同時查詢數(shù)據(jù),詳情可參考BlockingCache的源碼。 
/>

觸發(fā)緩存

配置一級緩存為 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 就會創(chuàng)建一個 session,并且在執(zhí)行完畢后關閉 session。所以兩次調用并不在一個 session 中,一級緩存并沒有發(fā)生作用。開啟事務,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 操作兩次但是只執(zhí)行了一次 sql ,緩存發(fā)揮了作用。這就跟一開始我遇到的那個 BUG 場景一樣:同一 session 且 select 調用 > 1 次。如果在兩次調用中間插入 update 操作,緩存會立即失效。只要 session 中有 insert、update 和 delete 語句,該 session 中的緩存會立即被刷新。但是注意這只是在同一 session 之間。不同 session 之間如 session1 和 session2,session1 里的 insert/update/delete 并不會影響 session 2 下的緩存,這在高并發(fā)或者分布式的情況下會產(chǎn)生臟數(shù)據(jù)。所以建議將一級緩存級別調成 statement。

配置一級緩存為 STATEMENT 級別

再次將(1)中的無事務和有事務的代碼分別執(zhí)行一遍,打印結果始終如下:

配置成 SATEMENT 后,一級緩存相當于被關閉了。STATEMENT 級別暫時不好模擬,但是我猜測 STATEMENT 級別即在同一執(zhí)行 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>

執(zhí)行 http://localhost:8080/getUser?id=1,打印結果如下:

從圖中紅框可以看出第二次查詢命中緩存,0.5 是命中率。再次執(zhí)行 http://localhost:8080/getUser?id=1

打印結果如下:

這次一次 sql 也沒執(zhí)行了,緩存命中率上升到 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("==========緩存被剔除,執(zhí)行查詢 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;
}

執(zhí)行 http://localhost:8080/getUser?id=1&id2=2 最后打印的結果如下:

太長了,拼接下:

可以看出二級緩存只能緩存一個對象且 5s 后就失效了,配置生效。緩存配置中還有一個重要的配置 type,該配置可以配置第三方的 cache,特別在高并發(fā)和分布式情況下。當然,使用更專業(yè)的分布式緩存才是王道,例如 redis 等。

總結

本來想總結點什么的,但是覺得推薦文章中總結的非常好,直接引用了:

  1. MyBatis一級緩存的生命周期和SqlSession一致。
  2. MyBatis一級緩存內(nèi)部設計簡單,只是一個沒有容量限定的HashMap,在緩存的功能性上有所欠缺。
  3. MyBatis的一級緩存最大范圍是SqlSession內(nèi)部,有多個SqlSession或者分布式的環(huán)境下,數(shù)據(jù)庫寫操作會引起臟數(shù)據(jù),建議設定緩存級別為Statement。
  4. MyBatis的二級緩存相對于一級緩存來說,實現(xiàn)了SqlSession之間緩存數(shù)據(jù)的共享,同時粒度更加的細,能夠到namespace級別,通過Cache接口實現(xiàn)類不同的組合,對Cache的可控性也更強。
  5. MyBatis在多表查詢時,極大可能會出現(xiàn)臟數(shù)據(jù),有設計上的缺陷,安全使用二級緩存的條件比較苛刻。
  6. 在分布式環(huán)境下,由于默認的MyBatis Cache實現(xiàn)都是基于本地的,分布式環(huán)境下必然會出現(xiàn)讀取到臟數(shù)據(jù),需要使用集中式緩存將MyBatis的Cache接口實現(xiàn),有一定的開發(fā)成本,直接使用Redis、Memcached等分布式緩存可能成本更低,安全性也更高。
  7. 個人建議MyBatis緩存特性在生產(chǎn)環(huán)境中進行關閉,單純作為一個ORM框架使用可能更為合適。

參考

聊聊MyBatis緩存機制

以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關文章

  • Java中HashMap集合的6種遍歷方式詳解

    Java中HashMap集合的6種遍歷方式詳解

    這篇文章主要介紹了Java中HashMap集合的6種遍歷方式詳解,HashMap?基于哈希表的?Map?接口實現(xiàn),是以?key-value?存儲形式存在,即主要用來存放鍵值對,HashMap?的實現(xiàn)不是同步的,這意味著它不是線程安全的,我們來看一下其遍歷方式,需要的朋友可以參考下
    2023-12-12
  • Java二維數(shù)組與動態(tài)數(shù)組ArrayList類詳解

    Java二維數(shù)組與動態(tài)數(shù)組ArrayList類詳解

    這篇文章主要給大家介紹了關于Java二維數(shù)組與動態(tài)數(shù)組ArrayList類的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-09-09
  • java中servlet實現(xiàn)登錄驗證的方法

    java中servlet實現(xiàn)登錄驗證的方法

    做web開發(fā),登錄驗證是免不了的,今天學習了servlet的登錄驗證,當然是很簡單的,沒有使用session,request等作用域對象,所以還是可以直接通過地址訪問網(wǎng)頁的。
    2013-05-05
  • Java線程的控制詳解

    Java線程的控制詳解

    這篇文章主要介紹了Java中的join線程、后臺線程、線程睡眠、線程讓步以及線程的優(yōu)先級,非常的詳細,希望能對大家有所幫助
    2014-10-10
  • SpringBoot整合RabbitMQ 手動應答(簡單demo)

    SpringBoot整合RabbitMQ 手動應答(簡單demo)

    這篇文章主要介紹了SpringBoot整合RabbitMQ 手動應答 簡單demo,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-01-01
  • 使用Feign遠程調用時,序列化對象失敗的解決

    使用Feign遠程調用時,序列化對象失敗的解決

    這篇文章主要介紹了使用Feign遠程調用時,序列化對象失敗的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • Mybatis中foreach標簽帶來的空格\換行\(zhòng)回車問題及解決方案

    Mybatis中foreach標簽帶來的空格\換行\(zhòng)回車問題及解決方案

    這篇文章主要介紹了解決Mybatis中foreach標簽帶來的空格,換行,回車問題,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-04-04
  • Springboot讀取templates文件html代碼實例

    Springboot讀取templates文件html代碼實例

    這篇文章主要介紹了Springboot讀取templates文件html代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-04-04
  • SpringBoot學習之基于注解的緩存

    SpringBoot學習之基于注解的緩存

    spring boot對緩存支持非常靈活,我們可以使用默認的EhCache,也可以整合第三方的框架,只需配置即可,下面這篇文章主要給大家介紹了關于SpringBoot學習之基于注解緩存的相關資料,需要的朋友可以參考下
    2022-03-03
  • springboot+mybatis配置clickhouse實現(xiàn)插入查詢功能

    springboot+mybatis配置clickhouse實現(xiàn)插入查詢功能

    這篇文章主要介紹了springboot+mybatis配置clickhouse實現(xiàn)插入查詢功能,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-08-08

最新評論

那坡县| 江门市| 翁牛特旗| 宁夏| 休宁县| 南木林县| 崇左市| 哈巴河县| 天台县| 罗山县| 岳阳县| 呼图壁县| 民勤县| 镇原县| 东乌珠穆沁旗| 诸城市| 樟树市| 邹平县| 武夷山市| 荔浦县| 靖江市| 徐州市| 西吉县| 永寿县| 射洪县| 平度市| 阿拉善盟| 木里| 邵阳市| 海淀区| 东源县| 行唐县| 安阳县| 锦屏县| 集贤县| 稻城县| 湟源县| 乌拉特后旗| 甘肃省| 杨浦区| 保康县|