Mybatis的緩存機制詳解

来源:https://www.cnblogs.com/eternityz/archive/2020/03/27/12584990.html
-Advertisement-
Play Games

目錄 一級緩存 二級緩存 自定義緩存 一級緩存 MyBatis 包含了一個非常強大的查詢緩存特性,它可以非常方便地配置和定製。MyBatis 3 中的緩存實現的很多改進都已經實現了,使得它更加強大而且易於配置。mybatis預設情況下只會開啟一級緩存,也就是局部的 session 會話緩存。 首先我 ...


目錄

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

一級緩存

MyBatis 包含了一個非常強大的查詢緩存特性,它可以非常方便地配置和定製。MyBatis 3 中的緩存實現的很多改進都已經實現了,使得它更加強大而且易於配置。mybatis預設情況下只會開啟一級緩存,也就是局部的 session 會話緩存。

首先我們要知道什麼是查詢緩存?查詢緩存又有什麼作用?

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

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

一級緩存是SqlSession級別的緩存。我們都知道在操作資料庫時需要構造 sqlSession對象,而在sqlSession對象中有一個數據結構(HashMap)用於存儲緩存數據。

如下圖:

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

我們可以來寫一個測試用例,測試一下同一個數據的第二次查詢是否沒有訪問資料庫,代碼如下:

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();
    }
}

控制台列印結果:

如上,可以看到只有第一次查詢訪問了資料庫。第二次查詢則沒有訪問資料庫,是從記憶體中直接讀取出來的數據。

我們上面也提到了,如果進行了增、刪、改的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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 工作中我們經常會有向別人描述項目內容,解釋某一個功能的運作過程等情況。除了文字描述外,配合圖的方式來說明,增加了問題的具象化能力,方便溝通和對方理解。我剛開始一直想用一種圖來表述所有內容,但是實際上沒有一種圖可以做到,即使可以做到,那麼會使得這種圖變得複雜,也不符合人類的接受能力。用對應的圖說明對應 ...
  • 圖解Java設計模式之模板模式 豆漿製作問題 模板方法模式基本介紹 模板方法模式原理類圖 模板方法模式解決豆漿製作問題 模板方法模式的鉤子方法 模板方法模式在Spring框架中的源碼分析 模板方法模式的註意事項和細節 豆漿製作問題 1)製作豆漿的流程 選材 – 》添加配料 --》浸泡 --》放到豆漿 ...
  • 目錄 . 一、基本概念 . 1、背景 . 2、簡介 . 3、特點 . 4、基礎模型 . 5、Apollo 的四個維度 . 6、本地緩存 . 7、客戶端設計 . 8、總體設計 . 9、可用性考慮 . 二、Apollo 配置中心創建項目與配置 . 1、登錄 Apollo . 2、修改與增加部門數據 . ...
  • 實現步驟 1.導包:import java.util.Scanner; 2.Scanner類的實例化:Scanner scan = new Scanner(System.in); 3.調用Scanner類的相關方法(next() nextInt())獲取指定類型的變數; 註意:在控制台,如果輸入的類 ...
  • 一、分類 順序結構:程式從上而下依次執行 分支結構:if-else if-else、switch-case 迴圈結構:while迴圈、for迴圈、do-while迴圈、增強for迴圈 二、具體說明 1.分支結構 1.1if分支結構 說明 1.else結構是可選的; 2.if-else結構可以嵌套使用 ...
  • 原文鏈接:http://www.yiidian.com/servlet/servlet how work.html 接下來我們有必要瞭解下Servlet的工作原理,這樣才能更好地理解Servlet。本文我們將以之前開發過的Servlet程式來講解Servlet的內部細節。 1 Servlet基本執行 ...
  • 1. 方法 註:class(類)是具有相同的屬性和方法的對象的集合。 2. 例子 (1)數據/集合類型 str(object=''); str(object=b'', encoding='utf-8', errors='strict') int(x, base=10) float(x=0) comp ...
  • 我用java爬蟲爬了一個圖片網站 最近想建立個網站,不想搞技術博客之類的網站了,因為像博客園還有CSDN這種足夠了。平時的問題也都是這些記錄一下就夠了。那搞個什麼網站好玩呢? 看到一個圖片網站還不錯,裡面好多圖片(當然有xxx圖片了....)哈哈,其實就是閑的,同時也介紹一下java爬蟲的相關用法把 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 在我們開發過程中基本上不可或缺的用到一些敏感機密數據,比如SQL伺服器的連接串或者是OAuth2的Secret等,這些敏感數據在代碼中是不太安全的,我們不應該在源代碼中存儲密碼和其他的敏感數據,一種推薦的方式是通過Asp.Net Core的機密管理器。 機密管理器 在 ASP.NET Core ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 順序棧的介面程式 目錄順序棧的介面程式頭文件創建順序棧入棧出棧利用棧將10進位轉16進位數驗證 頭文件 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> 創建順序棧 // 指的是順序棧中的元素的數據類型,用戶可以根據需要進行修改 ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • C總結與剖析:關鍵字篇 -- <<C語言深度解剖>> 目錄C總結與剖析:關鍵字篇 -- <<C語言深度解剖>>程式的本質:二進位文件變數1.變數:記憶體上的某個位置開闢的空間2.變數的初始化3.為什麼要有變數4.局部變數與全局變數5.變數的大小由類型決定6.任何一個變數,記憶體賦值都是從低地址開始往高地 ...
  • 如果讓你來做一個有狀態流式應用的故障恢復,你會如何來做呢? 單機和多機會遇到什麼不同的問題? Flink Checkpoint 是做什麼用的?原理是什麼? ...
  • C++ 多級繼承 多級繼承是一種面向對象編程(OOP)特性,允許一個類從多個基類繼承屬性和方法。它使代碼更易於組織和維護,並促進代碼重用。 多級繼承的語法 在 C++ 中,使用 : 符號來指定繼承關係。多級繼承的語法如下: class DerivedClass : public BaseClass1 ...
  • 前言 什麼是SpringCloud? Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的開發便利性簡化了分散式系統的開發,比如服務註冊、服務發現、網關、路由、鏈路追蹤等。Spring Cloud 並不是重覆造輪子,而是將市面上開發得比較好的模塊集成進去,進行封裝,從 ...
  • class_template 類模板和函數模板的定義和使用類似,我們已經進行了介紹。有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同。類模板用於實現類所需數據的類型參數化 template<class NameType, class AgeType> class Person { publi ...
  • 目錄system v IPC簡介共用記憶體需要用到的函數介面shmget函數--獲取對象IDshmat函數--獲得映射空間shmctl函數--釋放資源共用記憶體實現思路註意 system v IPC簡介 消息隊列、共用記憶體和信號量統稱為system v IPC(進程間通信機制),V是羅馬數字5,是UNI ...