Mybatis的緩存機制詳解

目錄

  • 一級緩存
  • 二級緩存
  • 自定義緩存

一級緩存

MyBatis 包含了一個很是強大的查詢緩存特性,它能夠很是方便地配置和定製。MyBatis 3 中的緩存實現的不少改進都已經實現了,使得它更增強大並且易於配置。mybatis默認狀況下只會開啓一級緩存,也就是局部的 session 會話緩存。java

首先咱們要知道什麼是查詢緩存?查詢緩存又有什麼做用?mysql

  • 功能:mybatis提供查詢緩存,用於減輕數據壓力,提升數據庫性能。

以下圖,每個 session 會話都會有各自的緩存,這緩存是局部的,也就是所謂的一級緩存:git

一級緩存是SqlSession級別的緩存。咱們都知道在操做數據庫時須要構造 sqlSession對象,而在sqlSession對象中有一個數據結構(HashMap)用於存儲緩存數據。算法

以下圖:sql

從圖上,咱們能夠看出,一級緩存區域是根據SqlSession爲單位劃分的。每次查詢都會先從緩存區域找,若是找不到就會從數據庫查詢數據,而後將查詢到的數據寫入一級緩存中。Mybatis內部存儲緩存使用的是一個HashMap對象,key爲 hashCode + sqlId + sql 語句。而value值就是從查詢出來映射生成的java對象。而爲了保證緩存裏面的數據確定是準確數據避免髒讀,每次咱們進行數據修改後(增、刪、改操做)就會執行commit操做,清空緩存區域。數據庫

咱們能夠來寫一個測試用例,測試一下同一個數據的第二次查詢是否沒有訪問數據庫,代碼以下:apache

package org.zero01.test;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.*;
import org.json.JSONObject;
import org.junit.Test;
import org.zero01.dao.StudentMapper;
import org.zero01.pojo.Student;

import java.io.IOException;
import java.io.InputStream;

public class TestMybatisCache {

    @Test
    public void testMybatisCache() throws IOException {
        String confPath = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(confPath);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);

        // 進行第一次查詢
        Student student1 = studentMapper.selectByPrimaryKey(1);
        System.out.println("sqlSession1 第一次查詢:" + new JSONObject(student1));

        // 進行第二次查詢
        Student student2 = studentMapper.selectByPrimaryKey(1);
        System.out.println("sqlSession1 第二次查詢:" + new JSONObject(student2));

        sqlSession.close();
    }
}

控制檯打印結果:json

如上,能夠看到只有第一次查詢訪問了數據庫。第二次查詢則沒有訪問數據庫,是從內存中直接讀取出來的數據。緩存

咱們上面也提到了,若是進行了增、刪、改的sql操做並進行了事務的commit提交操做後,SqlSession中的一級緩存就會被清空,不會致使髒數據的出現。一樣的,咱們可使用測試用例來演示這一點,修改測試代碼以下:安全

est
public void testMybatisCache() throws IOException {
    String confPath = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(confPath);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);

    // 進行第一次查詢
    Student student1 = studentMapper.selectByPrimaryKey(2);
    System.out.println("sqlSession1 第一次查詢:" + new JSONObject(student1));

    Student stuUpdate = new Student();
    stuUpdate.setSid(2);
    stuUpdate.setSname("渣渣輝");
    stuUpdate.setAge(21);
    int rowCount = studentMapper.updateByPrimaryKeySelective(stuUpdate);
    if (rowCount > 0) {
        sqlSession.commit();
        System.out.println("更新student數據成功");
    }

    // 進行第二次查詢
    Student student2 = studentMapper.selectByPrimaryKey(2);
    System.out.println("sqlSession1 第二次查詢:" + new JSONObject(student2));

    sqlSession.close();
}

控制檯打印結果:

如上,能夠看到當數據更新成功並commit後,會清空SqlSession中的一級緩存,第二次查詢就會訪問數據庫查詢最新的數據了。

不一樣的sqlSession之間的緩存數據區域(HashMap)是互相不影響的。因此在這種狀況下,是不能實現跨表的session共享的。有一點值得注意的是,因爲不一樣的sqlSession之間的緩存數據區域不共享,若是使用多個SqlSession對數據庫進行操做時,就會出現髒數據。咱們能夠修改以前的測試用例來演示這個現象,修改測試代碼以下:

@Test
public void testMybatisCache() throws IOException {
    String confPath = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(confPath);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();

    // 使用sqlSession1進行第一次查詢
    StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
    Student student = studentMapper.selectByPrimaryKey(1);
    System.out.println("sqlSession1 第一次查詢:" + new JSONObject(student));

    // 使用sqlSession2進行數據的更新
    StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
    Student student2 = new Student();
    student2.setSid(1);
    student2.setSname("渣渣輝");
    student2.setAge(21);
    int rowCount = studentMapper2.updateByPrimaryKeySelective(student2);
    if (rowCount > 0) {
        sqlSession2.commit();
        System.out.println("sqlSession2 更新student數據成功");
    }

    // 使用sqlSession1進行第二次查詢
    student = studentMapper.selectByPrimaryKey(1);
    System.out.println("sqlSession1 第二次查詢:" + new JSONObject(student));

    sqlSession1.close();
    sqlSession2.close();
}

控制檯打印結果:

sqlSession1 第一次查詢:{"address":"湖南","sname":"小明","sex":"男","age":16,"sid":1,"cid":1}
sqlSession2 更新student數據成功
sqlSession1 第二次查詢:{"address":"湖南","sname":"小明","sex":"男","age":16,"sid":1,"cid":1}

因而可知,Mybatis的一級緩存只存在於SqlSession中,能夠提升咱們的查詢性能,下降數據庫壓力,可是不能實現多sql的session共享,因此使用多個SqlSession操做數據庫會產生髒數據。

二級緩存

二級緩存是mapper級別的緩存,多個SqlSession去操做同一個Mapper的sql語句,多個SqlSession能夠共用二級緩存,二級緩存是能夠橫跨跨SqlSession的。

示意圖:

二級緩存區域是根據mapper的namespace劃分的,相同namespace的mapper查詢數據放在同一個區域,若是使用mapper代理方法每一個mapper的namespace都不一樣,此時能夠理解爲二級緩存區域是根據mapper劃分,也就是根據命名空間來劃分的,若是兩個mapper文件的命名空間同樣,那樣,不一樣的SqlSession之間就能夠共享一個mapper緩存。

示意圖:

在默認狀況下是沒有開啓二級緩存的,除了局部的 session 緩存。而在一級緩存中咱們也介紹了,不一樣的SqlSession之間的一級緩存是不共享的,因此若是咱們用兩個SqlSession去查詢同一個數據,都會往數據庫發送sql。這一點,咱們也能夠經過測試用例進行測試,測試代碼以下:

@Test
public void testMybatisCache2() throws IOException {
    String confPath = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(confPath);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();

    // 使用sqlSession1進行第一次查詢
    StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
    Student student = studentMapper.selectByPrimaryKey(1);
    System.out.println("sqlSession1 第一次查詢:" + new JSONObject(student));

    sqlSession1.close();

    // 使用sqlSession2進行第一次查詢
    StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
    Student student2 = studentMapper2.selectByPrimaryKey(1);
    System.out.println("sqlSession2 第一次查詢:" + new JSONObject(student2));

    sqlSession2.close();
}

控制檯輸出結果:

若是想要開啓二級緩存,你須要在你的mybatis主配置文件里加入:

<settings>
    <!-- 對在此配置文件下的全部cache進行全局性的開/關設置。默認值:true -->
    <setting name="cacheEnabled" value="true"/>
</settings>

而後在須要被緩存的 SQL 映射文件中添加一行cache配置便可:

...
<mapper namespace="org.zero01.dao.StudentMapper">
    ...
    <cache/>
    ...
</mapper>

字面上看就是這樣。這個簡單語句的效果以下:

  • 映射語句文件中的全部 select 語句將會被緩存。
  • 映射語句文件中的全部 insert,update 和 delete 語句會刷新緩存。
  • 緩存會使用 Least Recently Used(LRU,最近最少使用的)算法來收回。
  • 根據時間表(好比 no Flush Interval,沒有刷新間隔), 緩存不會以任什麼時候間順序 來刷新。
  • 緩存會存儲列表集合或對象(不管查詢方法返回什麼)的 1024 個引用。
  • 緩存會被視爲是 read/write(可讀/可寫)的緩存,意味着對象檢索不是共享的,而 且能夠安全地被調用者修改,而不干擾其餘調用者或線程所作的潛在修改。

注:緩存只適用於緩存標記所在的映射文件中聲明的語句。若是你使用的是java的API和XML映射文件一塊兒,默認狀況下不會緩存接口中聲明的語句。你須要把緩存區使用@CacheNamespaceRef註解進行聲明。

假如說,已開啓二級緩存的Mapper中有個statement要求禁用怎麼辦,那也不難,只須要在statement中設置useCache="false"就能夠禁用當前select語句的二級緩存,也就是每次都會生成sql去查詢,ps:默認狀況下默認是true,也就是默認使用二級緩存。以下示例:

<select id="findAll" resultMap="BaseResultMap" useCache="false">
    select
    <include refid="Base_Column_List"/>
    from
    student
</select>

除此以外,還有個flushCache屬性,該屬性用於刷新緩存,將其設置爲 true時,任什麼時候候只要語句被調用,都會致使一級緩存和二級緩存都會被清空,默認值:false。在mapper的同一個namespace中,若是有其餘insert、update、delete操做後都須要執行刷新緩存操做,來避免髒讀。這時咱們只須要設置statement配置中的flushCache="true"屬性,就會默認刷新緩存,相反若是是false就不會了。固然,無論開不開緩存刷新功能,你要是手動更改數據庫表,那都確定不能避免髒讀的發生。以下示例:

<select id="findAll" resultMap="BaseResultMap" flushCache="true">
    ...
</select>

那既然可以刷新緩存,能定時刷新嗎?也就是設置時間間隔來刷新緩存,答案是確定的。咱們在mapper映射文件中添加<cache/>來表示開啓緩存,因此咱們就能夠經過<cache/>元素的屬性來進行配置。好比:

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

這個更高級的配置建立了一個 FIFO 緩存,並每隔 60 秒刷新,存數結果對象或列表的 512 個引用,並且返回的對象被認爲是隻讀的,所以在不一樣線程中的調用者之間修改它們會致使衝突。

  • flushInterval(刷新間隔) 能夠被設置爲任意的正整數,並且它們表明一個合理的毫秒 形式的時間段。默認狀況是不設置,也就是沒有刷新間隔,緩存僅僅調用語句時刷新。
  • size(引用數目) 能夠被設置爲任意正整數,要記住你緩存的對象數目和你運行環境的 可用內存資源數目。默認值是 1024。
  • readOnly(只讀) 屬性能夠被設置爲 true 或 false。只讀的緩存會給全部調用者返回緩 存對象的相同實例。所以這些對象不能被修改。這提供了很重要的性能優點。可讀寫的緩存 會返回緩存對象的拷貝(經過序列化) 。這會慢一些,可是安全,所以默認是 false。

可用的收回策略有:

  • LRU – 最近最少使用的:移除最長時間不被使用的對象。(默認)
  • FIFO – 先進先出:按對象進入緩存的順序來移除它們。
  • SOFT – 軟引用:移除基於垃圾回收器狀態和軟引用規則的對象。
  • WEAK – 弱引用:更積極地移除基於垃圾收集器狀態和弱引用規則的對象。

開啓了二級緩存以後,咱們再來進行測試,可是在運行測試用例以前,咱們須要給pojo類加上實現序列化接口的代碼,否則在關閉SqlSession的時候就會報錯,代碼以下:

package org.zero01.pojo;

import java.io.Serializable;

public class Student implements Serializable {
    ...
}

測試代碼不變,運行後,控制檯輸出結果以下:

能夠看到,開啓二級緩存後,SqlSession之間的數據就能夠經過二級緩存共享了,和一級緩存同樣,當執行了insert、update、delete等操做並commit提交後就會清空二級緩存區域。當一級緩存和二級緩存同時存在時,會先訪問二級緩存,再去訪問各自的一級緩存,若是都沒有須要的數據,纔會往數據庫發送sql進行查詢。這一點,咱們也能夠經過測試用例來進行測試,測試代碼以下:

@Test
public void testMybatisCache() throws IOException {
    String confPath = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(confPath);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();

    // 使用sqlSession1進行第一次查詢
    StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
    Student student = studentMapper.selectByPrimaryKey(1);
    System.out.println("sqlSession1 第一次查詢:" + new JSONObject(student));

    // 使用sqlSession2進行數據的更新
    StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
    Student student2 = new Student();
    student2.setSid(1);
    student2.setSname("小明");
    student2.setAge(16);
    int rowCount = studentMapper2.updateByPrimaryKeySelective(student2);
    if (rowCount > 0) {
        sqlSession2.commit();
        System.out.println("sqlSession2 更新student數據成功");
    }

    // 使用sqlSession1進行第二次查詢
    student = studentMapper.selectByPrimaryKey(1);
    System.out.println("sqlSession1 第二次查詢:" + new JSONObject(student));

    // 使用sqlSession2進行第一次查詢
    student2 = studentMapper2.selectByPrimaryKey(1);
    System.out.println("sqlSession2 第一次查詢:" + new JSONObject(student2));

    // 關閉會話
    sqlSession1.close();
    sqlSession2.close();
}

運行測試代碼後,控制檯輸出結果以下:

經過此測試用例能夠看出兩點:

  • 1.Mybatis的二級緩存是跨Session的,每一個Mapper享有同一個二級緩存域,一樣,每次執行commit操做以後,會清空二級緩存區域。
  • 2.若是數據存在一級緩存的話,依舊會去一級緩存中讀取數據,這樣會發生髒讀現象,不過咱們能夠在相應的statement中,設置flushCache="true",這樣每次都會清除緩存,並向數據發送sql來進行查詢。

或者全局關閉本地、二級緩存:

<settings>
    <setting name="cacheEnabled" value="false"/>
    <setting name="localCacheScope" value="STATEMENT"/>
</settings>

可是在使用多個sqlSession操做數據庫的時候,還有一個須要注意的問題,那就是事務隔離級別,mysql的默認事務隔離級別是REPEATABLE-READ(可重複讀)。這樣當多個sqlsession操做同一個數據的時候,可能會致使兩個不一樣的事務查詢出來的數據不一致,例如,sqlsession1 在同一個事務中讀取了兩次數據,而 sqlsession2 在 sqlsession1 第一次查詢以後就更新了數據,那麼因爲可重複讀的緣由,sqlsession1 第二次查詢到的依舊是以前的數據。

咱們可使用測試用例來測試一下,首先得關閉緩存或者在相應的statement中設置flushCache屬性值爲true(此時沒有緩存),測試用例代碼以下:

@Test
public void testMybatisCache() throws IOException {
    String confPath = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(confPath);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();

    // 使用sqlSession1進行第一次查詢
    StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
    Student student = studentMapper.selectByPrimaryKey(1);
    System.out.println("sqlSession1 第一次查詢:" + new JSONObject(student));

    // 使用sqlSession2進行數據的更新
    StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
    Student student2 = new Student();
    student2.setSid(1);
    student2.setSname("小明");
    student2.setAge(16);
    int rowCount = studentMapper2.updateByPrimaryKeySelective(student2);
    if (rowCount > 0) {
        sqlSession2.commit();
        System.out.println("sqlSession2 更新student數據成功");
    }

    // 使用sqlSession1進行第二次查詢
    student = studentMapper.selectByPrimaryKey(1);
    System.out.println("sqlSession1 第二次查詢:" + new JSONObject(student));

    // 使用sqlSession2進行第一次查詢
    student2 = studentMapper2.selectByPrimaryKey(1);
    System.out.println("sqlSession2 第一次查詢:" + new JSONObject(student2));

    // 關閉會話
    sqlSession1.close();
    sqlSession2.close();
}

控制檯輸出結果:

這就是mysql默認事務隔離級別REPEATABLE-READ(可重複讀)致使的現象,這種隔離級別可以保證同一個事務的生命週期內,讀取的數據是一致的,可是兩個不一樣的事務之間讀取出來的數據就可能不一致。

不過,若是你但願在不一樣的事務的生命週期內讀取的數據一致的話,就須要把事務隔離級別改爲READ-COMMITTED(讀已提交),該級別會致使不可重複讀,也就是說在同一個事務的生命週期內讀取到的數據多是不一致的,而在兩個不一樣的事務之間讀取的數據則是一致的。一樣的咱們可使用測試用例進行測試,修改測試代碼以下:

@Test
public void testMybatisCache() throws IOException {
    String confPath = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(confPath);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    // 設置事務隔離級別爲讀已提交
    SqlSession sqlSession1 = sqlSessionFactory.openSession(TransactionIsolationLevel.READ_COMMITTED);
    SqlSession sqlSession2 = sqlSessionFactory.openSession(TransactionIsolationLevel.READ_COMMITTED);

    // 使用sqlSession1進行第一次查詢
    StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
    Student student = studentMapper.selectByPrimaryKey(1);
    System.out.println("sqlSession1 第一次查詢:" + new JSONObject(student));

    // 使用sqlSession2進行數據的更新
    StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
    Student student2 = new Student();
    student2.setSid(1);
    student2.setSname("阿基米德");
    student2.setAge(22);
    int rowCount = studentMapper2.updateByPrimaryKeySelective(student2);
    if (rowCount > 0) {
        sqlSession2.commit();
        System.out.println("sqlSession2 更新student數據成功");
    }

    // 使用sqlSession1進行第二次查詢
    student = studentMapper.selectByPrimaryKey(1);
    System.out.println("sqlSession1 第二次查詢:" + new JSONObject(student));

    // 使用sqlSession2進行第一次查詢
    student2 = studentMapper2.selectByPrimaryKey(1);
    System.out.println("sqlSession2 第一次查詢:" + new JSONObject(student2));

    // 關閉會話
    sqlSession1.close();
    sqlSession2.close();
}

控制檯輸出結果:

能夠看到,設置成讀已提交後,兩個事務在數據更新後查詢出來的數據是一致的了。至因而使用可重複讀仍是讀已提交,就取決於實際的業務需求了,若是但願同一個事務的生命週期內,讀取的數據是一致的,就使用可重複讀級別。若是但願兩個不一樣的事務之間查詢出來的數據是一致的,那麼就使用讀已提交級別。

自定義緩存

mybatis自身的緩存作的並不完美,不過除了使用mybatis自帶的二級緩存, 你也可使用你本身實現的緩存或者其餘第三方的緩存方案建立適配器來徹底覆蓋緩存行爲。因此它提供了使用自定義緩存的機會,咱們能夠選擇使用咱們喜歡的自定義緩存,下面將介紹一下,使用ehcache做爲mybatis的自定義緩存的具體步驟。

首先,要想使用mybatis自定義緩存,就必須讓自定義緩存類實現mybatis提供的Cache 接口(org.apache.ibatis.cache.Cache):

public interface Cache {
  // 獲取緩存編號
  String getId();

  // 獲取緩存對象的大小
  int getSize();

  // 保存key值緩存對象
  void putObject(Object key, Object value);

  // 經過kEY獲取值
  Object getObject(Object key);

  // 緩存中是否有某個key
  boolean hasKey(Object key);

  // 獲取緩存的讀寫鎖
  ReadWriteLock getReadWriteLock();

  // 經過key刪除緩存對象
  Object removeObject(Object key);

  // 清空緩存
  void clear();
}

咱們要使用ehcache作自定義緩存,就應該完成這個自定義緩存類,但mybatis的git上提供了相對於的適配包,咱們只須要下載便可,下面是適配包的maven依賴:

<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.1.0</version>
</dependency>

接着在相應的 mapper xml文件中配置相應的緩存實現類:

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

實現Cache接口的是EhcacheCache的父類AbstractEhcacheCache,咱們能夠看一下它的源碼:

package org.mybatis.caches.ehcache;

import java.util.concurrent.locks.ReadWriteLock;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import org.apache.ibatis.cache.Cache;

public abstract class AbstractEhcacheCache implements Cache {
    protected static CacheManager CACHE_MANAGER = CacheManager.create();
    protected final String id;
    protected Ehcache cache;

    public AbstractEhcacheCache(String id) {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        } else {
            this.id = id;
        }
    }

    public void clear() {
        this.cache.removeAll();
    }

    public String getId() {
        return this.id;
    }

    public Object getObject(Object key) {
        Element cachedElement = this.cache.get(key);
        return cachedElement == null ? null : cachedElement.getObjectValue();
    }

    public int getSize() {
        return this.cache.getSize();
    }

    public void putObject(Object key, Object value) {
        this.cache.put(new Element(key, value));
    }

    public Object removeObject(Object key) {
        Object obj = this.getObject(key);
        this.cache.remove(key);
        return obj;
    }

    public void unlock(Object key) {
    }

    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        } else if (obj == null) {
            return false;
        } else if (!(obj instanceof Cache)) {
            return false;
        } else {
            Cache otherCache = (Cache)obj;
            return this.id.equals(otherCache.getId());
        }
    }

    public int hashCode() {
        return this.id.hashCode();
    }

    public ReadWriteLock getReadWriteLock() {
        return null;
    }

    public String toString() {
        return "EHCache {" + this.id + "}";
    }

    public void setTimeToIdleSeconds(long timeToIdleSeconds) {
        this.cache.getCacheConfiguration().setTimeToIdleSeconds(timeToIdleSeconds);
    }

    public void setTimeToLiveSeconds(long timeToLiveSeconds) {
        this.cache.getCacheConfiguration().setTimeToLiveSeconds(timeToLiveSeconds);
    }

    public void setMaxEntriesLocalHeap(long maxEntriesLocalHeap) {
        this.cache.getCacheConfiguration().setMaxEntriesLocalHeap(maxEntriesLocalHeap);
    }

    public void setMaxEntriesLocalDisk(long maxEntriesLocalDisk) {
        this.cache.getCacheConfiguration().setMaxEntriesLocalDisk(maxEntriesLocalDisk);
    }

    public void setMemoryStoreEvictionPolicy(String memoryStoreEvictionPolicy) {
        this.cache.getCacheConfiguration().setMemoryStoreEvictionPolicy(memoryStoreEvictionPolicy);
    }
}

接着咱們還須要在resources目錄下,建立ehcache的配置文件:ehcache.xml,文件內容以下:

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

    <diskStore path="java.io.tmpdir"/>

    <!--
    Mandatory Default Cache configuration. These settings will be applied to caches
    created programmtically using CacheManager.add(String cacheName)
    -->
    <!--
       name:緩存名稱。
       maxElementsInMemory:緩存最大個數。
       eternal:對象是否永久有效,一但設置了,timeout將不起做用。
       timeToIdleSeconds:設置對象在失效前的容許閒置時間(單位:秒)。僅當eternal=false對象不是永久有效時使用,可選屬性,默認值是0,也就是可閒置時間無窮大。
       timeToLiveSeconds:設置對象在失效前容許存活時間(單位:秒)。最大時間介於建立時間和失效時間之間。僅當eternal=false對象不是永久有效時使用,默認是0.,也就是對象存活時間無窮大。
       overflowToDisk:當內存中對象數量達到maxElementsInMemory時,Ehcache將會對象寫到磁盤中。
       diskSpoolBufferSizeMB:這個參數設置DiskStore(磁盤緩存)的緩存區大小。默認是30MB。每一個Cache都應該有本身的一個緩衝區。
       maxElementsOnDisk:硬盤最大緩存個數。
       diskPersistent:是否緩存虛擬機重啓期數據 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
       diskExpiryThreadIntervalSeconds:磁盤失效線程運行時間間隔,默認是120秒。
       memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時,Ehcache將會根據指定的策略去清理內存。默認策略是LRU(最近最少使用)。你能夠設置爲FIFO(先進先出)或是LFU(較少使用)。
       clearOnFlush:內存數量最大時是否清除。
    -->
    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="5"
            timeToLiveSeconds="5"
            overflowToDisk="true"
            maxElementsOnDisk="10000000"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"
    />
</ehcache>

以上就完成了自定義緩存的配置,接下來咱們測試一下緩存是否生效,測試代碼以下:

@Test
public void testMybatisCache2() throws IOException {
    String confPath = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(confPath);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();

    // 使用sqlSession1進行第一次查詢
    StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
    Student student = studentMapper.selectByPrimaryKey(1);
    System.out.println("sqlSession1 第一次查詢:" + new JSONObject(student));

    sqlSession1.close();

    // 使用sqlSession2進行第一次查詢
    StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
    Student student2 = studentMapper2.selectByPrimaryKey(1);
    System.out.println("sqlSession2 第一次查詢:" + new JSONObject(student2));

    sqlSession2.close();
}

控制檯輸出結果:

能夠看到,sqlsession2 查詢數據的時候緩存命中率爲0.5,而且也沒有向數據庫發送sql語句,那麼就表明咱們配置的自定義緩存生效並能夠成功緩存數據了。

站在巨人的肩膀上摘蘋果:

https://blog.51cto.com/zero01/2103911

相關文章
相關標籤/搜索