SpringBoot - SpringCache緩存

一、概述

SpringCache本身是一個緩存體系的抽象實現,並沒有具體的緩存能力,要使用SpringCache還需要配合具體的緩存實現來完成。
雖然如此,但是SpringCache是所有Spring支持的緩存結構的基礎,而且所有的緩存的使用最後都要歸結於SpringCache。
它可以將方法的運行結果進行緩存;以後再要相同的數據,直接從緩存中獲取,不用調用方法;

二、緩存註解

SpringCache緩存功能的實現是依靠下面的這幾個註解完成的。

  • @EnableCaching:開啓緩存功能
  • @Cacheable:定義緩存,用於觸發緩存
  • @CachePut:定義更新緩存,觸發緩存更新
  • @CacheEvict:定義清除緩存,觸發緩存清除
  • @Caching:組合定義多種緩存功能
  • @CacheConfig:定義公共設置,位於class之上

1、@CacheConfig

該註解標註於之上,用於配置該類中會用到的一些公共的緩存相關配置。
@CacheConfig(cacheNames = “pers.liuchengyin.service.blogServiceImpl”)
cachNames的值pers.liuchengyin.service.blogServiceImpl就是這個類公共的緩存對象,就相當於一個名爲pers.liuchengyin.service.blogServiceImplMap對象,下文簡稱對象

2、@Cacheable

用在查詢的方法上,方法的返回值將被加入緩存。該註解標註的方法每次被調用前都會觸發緩存校驗,校驗指定參數的緩存是否已存在(已發生過相同參數的調用),若存在,直接返回緩存結果,否則執行方法內容,最後將方法執行結果保存到緩存中。

該註解有這些參數:valuecacheNameskeyconditionunlesskeyGeneratorcacheManagercacheResolver

valuecacheNames是兩個同等的參數。cacheNames是Spring4新增的,作爲value的別名,用於指定緩存對象,不是必須指定的。如果不指定則使用上面配置的CacheConfig配置的cacheNames

key就是緩存對象存儲在Map集合中的key值,不是必需,缺省按照方法的所有參數組合作 爲key值, 可以自己配置,但需要SpEL表達式。比如

@Cacheable(key = "#p0")    // 表示第一個參數作爲緩存的key值
getBlog(Integer id, String name){}
@Cacheable(key = "#id")    // 也可以用參數名作爲緩存的key值,這裏的參數是id
getBlog(Integer id, String name){}
@Cacheable(key = "#user.id")   // 甚至可以用對象裏的某個屬性作爲key值
getBlog(Integer id, User user){}

condition緩存對象的條件,非必需,也需使用SpEL表達式,只有滿足表達式條件的內容纔會被緩存。比如

@Cacheable(key = "#p0",condition = "#username.lenth < 3")
getBlog(Integer id, String username){}		// 只有第二個參數長度小於3的時候才緩存進去

unlesscondition相似,但unless是方法被調用之後才做判斷的,所以可以對結果result進行判斷是否去緩存。

keyGenerator用於指定key 生成器,非必需。
若需要指定一個自定義的key生成器,我們需要去實現 org.springframework.cache.interceptor.KeyGenerator接口,並使用該參數來指定

cacheManager用於指定使用哪個緩存管理器,非必需。只有當有多個緩存管理器時才需要使用。

cacheResolver用於指定使用那個緩存解析器,非必需。需通過 org.springframework.cache.interceptor.CacheResolver接口來實現自己的緩存解析器,並用該參數指定。

3、@CachePut

用於更新緩存,無論結果是否已經緩存,都會在方法執行結束插入緩存,相當於更新緩存,一般用於更新方法新增方法之上。其參數與@Cacheable類似。

4、@CacheEvict

配置於方法上,通常用在刪除方法上,用來從緩存中移除相應數據。 除了同@Cacheable一樣的參數之外,它還有下面兩個參數:
allEntries,非必需,默認爲false。當爲true時,會移除對應緩存對象的所有緩存數據,也就是移除cacheNamevalue指定的對象的緩存數據。
beforeInvocation,非必需, 默認爲false,會在調用方法之後移除數據。 當爲 true時,會在調用方法之前移除數據。

5、@Caching

配置於方法上,用於定義複雜的緩存規則,比如

@Caching(
	cacheable={
		@Cacheable(key = "#name")
	},
	put={
		@CachePut("#result.id"),
		@CachePut("#result.email")
	}
)
Blog getByBlogNmae(String name){}

6、小結

一般來說,我們都在Service實現層的類上面使用@CacheConfig來指定公共緩存配置,在查詢的方法上使用@Cacheable,在新增/修改的方法上使用@CachePut,在刪除的方法上使用@CacheEvict

三、原理

SpringBoot中的自動配置類CacheAutoConfiguration中的緩存配置類有如下幾種:

org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration
org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration
org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration

其中,SimpleCacheConfiguration是默認生效的。它的作用就是給容器中註冊一個CacheManager:ConcurrentMapCacheManager。這個Manager可以獲取和創建ConcurrentMapCache類型的組件,可以將數據保存到ConcurrenMap中。
CacheManager緩存管理器,它是來管理多個Cache組件的,對於緩存的CRUD操作實際上是在Cache組件中完成的。前面所說的cacheName/value就是每一個Cache組件的名字。key也就是緩存的數據,也是就是說Cache組件裏是Key-value鍵值對。這些鍵值對是以數組的形式存儲在它們所對應的Cache組件裏的。這個Key和前面所說的keyGenerator只需選擇一個就行,一個是我們指定key,一個是根據生成器自動生成。

四、運行流程

①@Cacheable

1、方法運行之前,先去查詢Cache(緩存組件),按照cacheNames指定的名字獲取。
CacheManager先獲取相應的緩存,第一次獲取緩存如果沒有Cache組件會自動創建。
2、去Cache中查找緩存的內容
3、沒有查到緩存就調用目標方法
4、將目標方法返回的結果返回並放進緩存中
5、下一次來調用,因爲有這個緩存值,就不會再調用目標方法,直接返回緩存中的值

②@CachePut

1、先調用目標方法 - 這裏面就可能涉及數據庫的數據修改
2、將目標方法的結果緩存起來(也就是覆蓋老的緩存)
該註解所指定的Cache組件需要與對應@Cacheable的一致,不然就起不到修改的作用。

五、使用示例

1、引入依賴

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
	<groupId>net.sf.ehcache</groupId>
	<artifactId>ehcache</artifactId>
</dependency>

2、配置ehcache.xml

ehcache.xml是緩存的配置文件,默認在resources下,可以通過yml配置文件修改其讀取位置。

spring:
  cache:
    ehcache:
      config:claspath:config/ehcache_config.xml  # config文件夾下的echache_config.xml

這裏我們就使用默認的

<ehcache>
    <diskStore path="java.io.tmpdir/cache"/>
    <!-- 默認緩存策略 -->
    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="false"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
    />

    <!--
        自定義緩存策略
        name:緩存名稱
        maxElementsInMemory:緩存最大個數,0表示無窮大
        eternal:緩存對象是否永久有效,如果爲true,忽略timeToIdleSeconds、timeToLiveSeconds
        timeToIdleSeconds:允許對象處於空閒狀態的最長時間,以秒爲單位
        timeToLiveSeconds:對象允許存在於緩存中的最長時間,以秒爲單位
        overflowToDisk:true表示當內存緩存的對象數目達到了maxElementsInMemory界限後,會把溢出的對象寫到硬盤緩存中,對象需實現序列化接口
        diskPersistent:是否緩存虛擬機重啓期數據,是否持久化磁盤緩存
        diskExpiryThreadIntervalSeconds:磁盤失效線程運行時間間隔,以秒爲單位
    -->
    <cache name="student_cache"
           maxElementsInMemory="10000"
           eternal="true"
           timeToIdleSeconds="120"
           timeToLiveSeconds="120"
           overflowToDisk="true"
           diskPersistent="true"
           diskExpiryThreadIntervalSeconds="600"
    />
</ehcache>

3、創建POJO類

@Data
public class Student implements Serializable {
    /** ID */
    private Long id;
    /** 姓名 */
    private String name;
    /** 性別 */
    private String sex;
    /** 班級 */
    protected String classGrade;
    /** 入學日期 */
    private Date admissionDate;
}

4、創建Service類

@Service
@CacheConfig(cacheNames = "student_cache")   // 緩存名
public class StudentServiceImpl implements StudentService {

    @Override
    @Cacheable  // 默認以參數組合爲Key
    public Student getStudentById(Long id) {
        System.out.println("直接獲取學生信息:");
        Student student = new Student();
        student.setId(1L);
        student.setName("柳成蔭");
        student.setSex("男");
        student.setClassGrade("21班");
        student.setAdmissionDate(new Date());
        return student;
    }

    @Override
    @CachePut(key = "#student.id")   // 指定對象的Id爲Key
    public Student updateStudent(Student student) {
        System.out.println("修改學生信息:");
        student.setName("九月清晨");
        return student;
    }

    @Override
    @CacheEvict(key = "#id")        // 刪除Id爲Key的緩存
    public void deleteStudentById(Long id) {
        System.out.println("刪除學生信息:");
    }
    
}

5、測試

爲了方便測試,直接使用SpringBootTest來進行測試,直接調用Service。

@RunWith(SpringRunner.class)
@SpringBootTest
public class DoTest {

    @Autowired
    private StudentService studentService;

    @Test
    public void test1(){
        // 調用查詢信息
        Student student = studentService.getStudentById(1L);
        System.out.println(student);
        System.out.println("--------------------分割線-----------------------");
        // 調用查詢信息
        Student student1 = studentService.getStudentById(1L);
        System.out.println(student1);
        System.out.println("--------------------分割線-----------------------");
        // 修改學生信息
        Student student2 = studentService.updateStudent(student1);
        System.out.println(student2);
        System.out.println("--------------------分割線-----------------------");
        // 調用查詢信息
        Student student3 = studentService.getStudentById(1L);
        System.out.println(student3);
        System.out.println("--------------------分割線-----------------------");
        // 刪除學生信息
        studentService.deleteStudentById(1L);
        System.out.println("--------------------分割線-----------------------");
        // 調用查詢信息
        Student student4 = studentService.getStudentById(1L);
        System.out.println(student4);
    }

}

6、運行結果分析

運行結果
從運行截圖和Service裏的方法來看:
1、第一次查詢時,發現是調用了方法的
2、第二次查詢時,沒有出現直接獲取學生信息這一句,說明是從緩存中取的結果
3、接着,調用修改學生信息的方法,name被修改成九月清晨
4、第三次查詢時,發現name變成了九月清晨,依舊是從緩存中獲取的,說明修改了緩存
5、接着,調用刪除學生信息的方法
6、第四次查詢時,出現直接獲取學生信息這一句,並且name變爲柳成蔭,說明刪除緩存了,直接調用方法獲取的結果

六、自定義緩存Key

Spring提供了一個root對象生成Key,如下圖:
root
除此之外,我們也可也自定義緩存Key的生成器KeyGenerator,如下

@Component
public class MyKeyGenerator implements KeyGenerator {
    /**
     * 自定義生成Key
     * @param target 當前對象
     * @param method 當前請求的方法
     * @param params 方法的參數
     * @return
     */
    @Override
    public Object generate(Object target, Method method, Object... params) {
        String className = target.getClass().getSimpleName();
        String methodName = method.getName();
        String args = "";
        for (Object param : params) {
            if (param instanceof Long){
                args += param;
            }else if(param instanceof Student){
                Student student = (Student) param;
                args += student.getName();
            }
        }
        // 這只是個示例,根據自己需求進行返回Key
        return className + methodName + args;
    }
}

在使用的時候,我們只需要指定生成策略即可:

@Autowired
private MyKeyGenerator myKeyGenerator;

@Cacheable(keyGenerator = "myKeyGenerator")
public void getStudent(Long id, Student student){
	System.out.println("....");
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章