mybatis 一級緩存、二級緩存

 mybatis 爲我們提供了一級緩存和二級緩存,可以通過下圖來理解:

  

  ①、一級緩存是SqlSession級別的緩存。在操作數據庫時需要構造sqlSession對象,在對象中有一個數據結構(HashMap)用於存儲緩存數據。不同的sqlSession之間的緩存數據區域(HashMap)是互相不影響的。

  ②、二級緩存是mapper級別的緩存,多個SqlSession去操作同一個Mapper的sql語句,多個SqlSession可以共用二級緩存,二級緩存是跨SqlSession的。

 

回到頂部

1、一級緩存

  ①、我們在一個 sqlSession 中,對 User 表根據id進行兩次查詢,查看他們發出sql語句的情況。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

@Test

public void testSelectOrderAndUserByOrderId(){

    //根據 sqlSessionFactory 產生 session

    SqlSession sqlSession = sessionFactory.openSession();

    String statement = "one.to.one.mapper.OrdersMapper.selectOrderAndUserByOrderID";

    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    //第一次查詢,發出sql語句,並將查詢的結果放入緩存中

    User u1 = userMapper.selectUserByUserId(1);

    System.out.println(u1);

     

    //第二次查詢,由於是同一個sqlSession,會在緩存中查找查詢結果

    //如果有,則直接從緩存中取出來,不和數據庫進行交互

    User u2 = userMapper.selectUserByUserId(1);

    System.out.println(u2);

     

    sqlSession.close();

}

  查看控制檯打印情況:

 

  ②、 同樣是對user表進行兩次查詢,只不過兩次查詢之間進行了一次update操作。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

@Test

public void testSelectOrderAndUserByOrderId(){

    //根據 sqlSessionFactory 產生 session

    SqlSession sqlSession = sessionFactory.openSession();

    String statement = "one.to.one.mapper.OrdersMapper.selectOrderAndUserByOrderID";

    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    //第一次查詢,發出sql語句,並將查詢的結果放入緩存中

    User u1 = userMapper.selectUserByUserId(1);

    System.out.println(u1);

     

    //第二步進行了一次更新操作,sqlSession.commit()

    u1.setSex("女");

    userMapper.updateUserByUserId(u1);

    sqlSession.commit();

     

    //第二次查詢,由於是同一個sqlSession.commit(),會清空緩存信息

    //則此次查詢也會發出 sql 語句

    User u2 = userMapper.selectUserByUserId(1);

    System.out.println(u2);

     

    sqlSession.close();

}

  控制檯打印情況:

  

  ③、總結

  1、第一次發起查詢用戶id爲1的用戶信息,先去找緩存中是否有id爲1的用戶信息,如果沒有,從數據庫查詢用戶信息。得到用戶信息,將用戶信息存儲到一級緩存中。 

  2、如果中間sqlSession去執行commit操作(執行插入、更新、刪除),則會清空SqlSession中的一級緩存,這樣做的目的爲了讓緩存中存儲的是最新的信息,避免髒讀。

  3、第二次發起查詢用戶id爲1的用戶信息,先去找緩存中是否有id爲1的用戶信息,緩存中有,直接從緩存中獲取用戶信息。

   

 

 

 

 

回到頂部

2、二級緩存

  二級緩存的原理和一級緩存原理一樣,第一次查詢,會將數據放入緩存中,然後第二次查詢則會直接去緩存中取。但是一級緩存是基於 sqlSession 的,而 二級緩存是基於 mapper文件的namespace的,也就是說多個sqlSession可以共享一個mapper中的二級緩存區域,並且如果兩個mapper的namespace相同,即使是兩個mapper,那麼這兩個mapper中執行sql查詢到的數據也將存在相同的二級緩存區域中。

   

  那麼二級緩存是如何使用的呢?

  ①、開啓二級緩存

  和一級緩存默認開啓不一樣,二級緩存需要我們手動開啓

  首先在全局配置文件 mybatis-configuration.xml 文件中加入如下代碼:

1

2

3

4

<!--開啓二級緩存  -->

<settings>

    <setting name="cacheEnabled" value="true"/>

</settings>

  其次在 UserMapper.xml 文件中開啓緩存

1

2

<!-- 開啓二級緩存 -->

<cache></cache>

  我們可以看到 mapper.xml 文件中就這麼一個空標籤<cache/>,其實這裏可以配置<cache type="org.apache.ibatis.cache.impl.PerpetualCache"/>,PerpetualCache這個類是mybatis默認實現緩存功能的類。我們不寫type就使用mybatis默認的緩存,也可以去實現 Cache 接口來自定義緩存。

  

  

  我們可以看到 二級緩存 底層還是 HashMap 架構。

 

 

   ②、po 類實現 Serializable 序列化接口

   

 

   開啓了二級緩存後,還需要將要緩存的pojo實現Serializable接口,爲了將緩存數據取出執行反序列化操作,因爲二級緩存數據存儲介質多種多樣,不一定只存在內存中,有可能存在硬盤中,如果我們要再取這個緩存的話,就需要反序列化了。所以mybatis中的pojo都去實現Serializable接口。

   

   ③、測試

   一、測試二級緩存和sqlSession 無關

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

@Test

public void testTwoCache(){

    //根據 sqlSessionFactory 產生 session

    SqlSession sqlSession1 = sessionFactory.openSession();

    SqlSession sqlSession2 = sessionFactory.openSession();

     

    String statement = "com.ys.twocache.UserMapper.selectUserByUserId";

    UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);

    UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);

    //第一次查詢,發出sql語句,並將查詢的結果放入緩存中

    User u1 = userMapper1.selectUserByUserId(1);

    System.out.println(u1);

    sqlSession1.close();//第一次查詢完後關閉sqlSession

     

    //第二次查詢,即使sqlSession1已經關閉了,這次查詢依然不發出sql語句

    User u2 = userMapper2.selectUserByUserId(1);

    System.out.println(u2);

    sqlSession2.close();

}

  可以看出上面兩個不同的sqlSession,第一個關閉了,第二次查詢依然不發出sql查詢語句。

 

  二、測試執行 commit() 操作,二級緩存數據清空

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

@Test

public void testTwoCache(){

    //根據 sqlSessionFactory 產生 session

    SqlSession sqlSession1 = sessionFactory.openSession();

    SqlSession sqlSession2 = sessionFactory.openSession();

    SqlSession sqlSession3 = sessionFactory.openSession();

     

    String statement = "com.ys.twocache.UserMapper.selectUserByUserId";

    UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);

    UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);

    UserMapper userMapper3 = sqlSession2.getMapper(UserMapper.class);

    //第一次查詢,發出sql語句,並將查詢的結果放入緩存中

    User u1 = userMapper1.selectUserByUserId(1);

    System.out.println(u1);

    sqlSession1.close();//第一次查詢完後關閉sqlSession

     

    //執行更新操作,commit()

    u1.setUsername("aaa");

    userMapper3.updateUserByUserId(u1);

    sqlSession3.commit();

     

    //第二次查詢,由於上次更新操作,緩存數據已經清空(防止數據髒讀),這裏必須再次發出sql語句

    User u2 = userMapper2.selectUserByUserId(1);

    System.out.println(u2);

    sqlSession2.close();

}

  查看控制檯情況:

 

   ④、useCache和flushCache

  mybatis中還可以配置userCache和flushCache等配置項,userCache是用來設置是否禁用二級緩存的,在statement中設置useCache=false可以禁用當前select語句的二級緩存,即每次查詢都會發出sql去查詢,默認情況是true,即該sql使用二級緩存。

1

2

3

<select id="selectUserByUserId" useCache="false" resultType="com.ys.twocache.User" parameterType="int">

    select * from user where id=#{id}

</select>

  這種情況是針對每次查詢都需要最新的數據sql,要設置成useCache=false,禁用二級緩存,直接從數據庫中獲取。 

  在mapper的同一個namespace中,如果有其它insert、update、delete操作數據後需要刷新緩存,如果不執行刷新緩存會出現髒讀。

    設置statement配置中的flushCache=”true” 屬性,默認情況下爲true,即刷新緩存,如果改成false則不會刷新。使用緩存時如果手動修改數據庫表中的查詢數據會出現髒讀。

1

2

3

<select id="selectUserByUserId" flushCache="true" useCache="false" resultType="com.ys.twocache.User" parameterType="int">

    select * from user where id=#{id}

</select>

  一般下執行完commit操作都需要刷新緩存,flushCache=true表示刷新緩存,這樣可以避免數據庫髒讀。所以我們不用設置,默認即可。

 

 

回到頂部

3、二級緩存整合ehcache

  上面我們介紹了mybatis自帶的二級緩存,但是這個緩存是單服務器工作,無法實現分佈式緩存。那麼什麼是分佈式緩存呢?假設現在有兩個服務器1和2,用戶訪問的時候訪問了1服務器,查詢後的緩存就會放在1服務器上,假設現在有個用戶訪問的是2服務器,那麼他在2服務器上就無法獲取剛剛那個緩存,如下圖所示:

  

  爲了解決這個問題,就得找一個分佈式的緩存,專門用來存儲緩存數據的,這樣不同的服務器要緩存數據都往它那裏存,取緩存數據也從它那裏取,如下圖所示:

  

 

   如上圖所示,在幾個不同的服務器之間,我們使用第三方緩存框架,將緩存都放在這個第三方框架中,然後無論有多少臺服務器,我們都能從緩存中獲取數據。

  這裏我們介紹mybatis與第三方框架ehcache的整合。

  上文一開始提到過,mybatis提供了一個cache接口,如果要實現自己的緩存邏輯,實現cache接口開發即可。mybatis本身默認實現了一個,但是這個緩存的實現無法實現分佈式緩存,所以我們要自己來實現。ehcache分佈式緩存就可以,mybatis提供了一個針對cache接口的ehcache實現類,這個類在mybatis和ehcache的整合包中。

  ①、導入 mybatis-ehcache 整合包(最上面的源代碼中包含有)

  

 

   ②、在全局配置文件 mybatis-configuration.xml 開啓緩存

1

2

3

4

<!--開啓二級緩存  -->

<settings>

    <setting name="cacheEnabled" value="true"/>

</settings>

  

   ③、在 xxxMapper.xml 文件中整合 ehcache 緩存

  將如下的類的全類名寫入<cache type="" ></cache>的type屬性中

1

2

3

4

5

<!-- 開啓本mapper的namespace下的二級緩存

    type:指定cache接口的實現類的類型,不寫type屬性,mybatis默認使用PerpetualCache

    要和ehcache整合,需要配置type爲ehcache實現cache接口的類型

-->

<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>

  

  ④、配置緩存參數

  在 classpath 目錄下新建一個 ehcache.xml 文件,並增加如下配置:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

<?xml version="1.0" encoding="UTF-8"?>

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">

 

    <diskStore path="F:\develop\ehcache"/>

 

    <defaultCache

            maxElementsInMemory="10000"

            eternal="false"

            timeToIdleSeconds="120"

            timeToLiveSeconds="120"

            maxElementsOnDisk="10000000"

            diskExpiryThreadIntervalSeconds="120"

            memoryStoreEvictionPolicy="LRU">

        <persistence strategy="localTempSwap"/>

    </defaultCache>

     

</ehcache>

  

 diskStore:指定數據在磁盤中的存儲位置。
 defaultCache:當藉助CacheManager.add("demoCache")創建Cache時,EhCache便會採用<defalutCache/>指定的的管理策略
以下屬性是必須的:
 maxElementsInMemory - 在內存中緩存的element的最大數目 
 maxElementsOnDisk - 在磁盤上緩存的element的最大數目,若是0表示無窮大
 eternal - 設定緩存的elements是否永遠不過期。如果爲true,則緩存的數據始終有效,如果爲false那麼還要根據timeToIdleSeconds,timeToLiveSeconds判斷
 overflowToDisk - 設定當內存緩存溢出的時候是否將過期的element緩存到磁盤上
以下屬性是可選的:
 timeToIdleSeconds - 當緩存在EhCache中的數據前後兩次訪問的時間超過timeToIdleSeconds的屬性取值時,這些數據便會刪除,默認值是0,也就是可閒置時間無窮大
 timeToLiveSeconds - 緩存element的有效生命期,默認是0.,也就是element存活時間無窮大
 diskSpoolBufferSizeMB 這個參數設置DiskStore(磁盤緩存)的緩存區大小.默認是30MB.每個Cache都應該有自己的一個緩衝區.
 diskPersistent - 在VM重啓的時候是否啓用磁盤保存EhCache中的數據,默認是false。
 diskExpiryThreadIntervalSeconds - 磁盤緩存的清理線程運行間隔,默認是120秒。每個120s,相應的線程會進行一次EhCache中數據的清理工作
 memoryStoreEvictionPolicy - 當內存緩存達到最大,有新的element加入的時候, 移除緩存中element的策略。默認是LRU(最近最少使用),可選的有LFU(最不常使用)和FIFO(先進先出)

 

 

回到頂部

4、二級緩存的應用場景

  對於訪問多的查詢請求且用戶對查詢結果實時性要求不高,此時可採用mybatis二級緩存技術降低數據庫訪問量,提高訪問速度,業務場景比如:耗時較高的統計分析sql、電話賬單查詢sql等。實現方法如下:通過設置刷新間隔時間,由mybatis每隔一段時間自動清空緩存,根據數據變化頻率設置緩存刷新間隔flushInterval,比如設置爲30分鐘、60分鐘、24小時等,根據需求而定。 

  mybatis二級緩存對細粒度的數據級別的緩存實現不好,比如如下需求:對商品信息進行緩存,由於商品信息查詢訪問量大,但是要求用戶每次都能查詢最新的商品信息,此時如果使用mybatis的二級緩存就無法實現當一個商品變化時只刷新該商品的緩存信息而不刷新其它商品的信息,因爲mybaits的二級緩存區域以mapper爲單位劃分的,當一個商品信息變化會將所有商品信息的緩存數據全部清空。解決此類問題可能需要在業務層根據需求對數據有針對性緩存。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章