前言
我知道在接口api項目中,頻繁的調用接口獲取數據,查詢數據庫是非常耗費資源的,於是就有了緩存技術,可以把一些不常更新,或者經常使用的數據,緩存起來,然后下次再請求時候,就直接從緩存中獲取,不需要再去查詢數據,這樣可以提供程序性能,增加用戶體驗,也節省服務資源浪費開銷,
在springboot幫你我們做好了整合,有對應的場景啟動器start,我們之間引入使用就好了,幫我們整合了各種緩存
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
</dependencies>
簡介
緩存介紹
Spring 從 3.1 開始就引入了對 Cache 的支持。定義了 org.springframework.cache.Cache 和 org.springframework.cache.CacheManager 接口來統一不同的緩存技術。並支持使用 JCache(JSR-107)注解簡化我們的開發。
其使用方法和原理都類似於 Spring 對事務管理的支持。Spring Cache 是作用在方法上的,其核心思想是,當我們在調用一個緩存方法時會把該方法參數和返回結果作為一個鍵值對存在緩存中。
Cache 和 CacheManager 接口說明
Cache 接口包含緩存的各種操作集合,你操作緩存就是通過這個接口來操作的。
Cache 接口下 Spring 提供了各種 xxxCache 的實現,比如:RedisCache、EhCache、ConcurrentMapCache
CacheManager 定義了創建、配置、獲取、管理和控制多個唯一命名的 Cache。這些 Cache 存在於 CacheManager 的上下文中。
小結
每次調用需要緩存功能的方法時,Spring 會檢查指定參數的指定目標方法是否已經被調用過,如果有就直接從緩存中獲取方法調用后的結果,如果沒有就調用方法並緩存結果后返回給用戶。下次調用直接從緩存中獲取。
使用Spring緩存抽象時我們需要關注以下兩點;
- 確定方法需要被緩存以及他們的緩存策略
- 從緩存中讀取之前緩存存儲的數據
快速開始
- 使用緩存我們需要開啟基於注解的緩存,使用
@EnableCaching標注在 springboot 主啟動類上或者配置類上
@SpringBootApplication
@MapperScan(value = {"cn.soboys.kmall.mapper","cn.soboys.kmall.sys.mapper","cn.soboys.kmall.security.mapper"},nameGenerator = UniqueNameGenerator.class)
@ComponentScan(value = {"cn.soboys.kmall"},nameGenerator = UniqueNameGenerator.class)
@EnableCaching //開啟緩存注解驅動,否則后面使用的緩存都是無效的
public class WebApplication {
private static ApplicationContext applicationContext;
public static void main(String[] args) {
applicationContext =
SpringApplication.run(WebApplication.class, args);
//displayAllBeans();
}
/**
* 打印所以裝載的bean
*/
public static void displayAllBeans() {
String[] allBeanNames = applicationContext.getBeanDefinitionNames();
for (String beanName : allBeanNames) {
System.out.println(beanName);
}
}
}
或者配置類
/**
* @author kenx
* @version 1.0
* @date 2021/8/17 15:05
* @webSite https://www.soboys.cn/
* 自定義緩存配置
*/
@Configuration
@Slf4j
@EnableCaching //開啟緩存注解驅動,否則后面使用的緩存都是無效的
public class CacheConfig {
//自定義配置類配置keyGenerator
@Bean("myKeyGenerator")
public KeyGenerator keyGenerator(){
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
return method.getName()+"["+ Arrays.asList(params).toString() +"]";
}
};
}
}
- 標注緩存注解在需要緩存的地方使用
@Cacheable注解
@CacheConfig(cacheNames = "menuCache",keyGenerator ="myKeyGenerator" )
public interface IMenuService extends IService<Menu> {
/**
* 獲取用戶菜單信息
*
* @param username 用戶名
* @return
*/
@Cacheable(key = "#username")
List<Menu> getUserMenus(String username);
}
@Cacheable注解有如下一些參數我們可以看到他源碼
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
@AliasFor("cacheNames")
String[] value() default {};
@AliasFor("value")
String[] cacheNames() default {};
String key() default "";
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
String condition() default "";
String unless() default "";
boolean sync() default false;
}
下面介紹一下 @Cacheable 這個注解常用的幾個屬性:
-
cacheNames/value :指定緩存組件的名字;將方法的返回結果放在哪個緩存中,是數組的方式,可以指定 多個緩存;
-
key :緩存數據時使用的 key,可以用它來指定。默認是使用方法參數的值。(這個 key 你可以使用 spEL 表達式來編寫如
#i d;參數id的值 #a0 #p0 #root.args[0]) -
keyGenerator :key的生成器;可以自己指定key的生成器的組件id 然后key 和 keyGenerator 二選一使用
-
cacheManager :可以用來指定緩存管理器。從哪個緩存管理器里面獲取緩存。或者cacheResolver指定獲取解析器
-
condition :可以用來指定符合條件的情況下才緩存
condition = "#id>0"
condition = "#a0>1":第一個參數的值》1的時候才進行緩存
- unless :否定緩存。當 unless 指定的條件為 true ,方法的返回值就不會被緩存。當然你也可以獲取到結果進行判斷。(通過 #result 獲取方法結果)
unless = "#result == null"
unless = "#a0==2":如果第一個參數的值是2,結果不緩存;
- sync :是否使用異步模式。異步模式的情況下
unless不支持 默認是方法執行完,以同步的方式將方法返回的結果存在緩存中
cacheNames/value屬性
用來指定緩存組件的名字,將方法的返回結果放在哪個緩存中,可以是數組的方式,支持指定多個緩存
/**
* 獲取用戶菜單信息
*
* @param username 用戶名
* @return
*/
@Cacheable(cacheNames = "menuCache") 或者// @Cacheable(cacheNames = {"menuCache","neCacge"})
List<Menu> getUserMenus(String username);
如果只有一個屬性,cacheNames可忽略,直接是value屬性默認
key
緩存數據時使用的 key。默認使用的是方法參數的值。可以使用 spEL 表達式去編寫。
Cache SpEL available metadata
| 名稱 | 位置 | 描述 | 示例 |
|---|---|---|---|
| methodName | root對象 | 當前被調用的方法名 | #root.methodname |
| method | root對象 | 當前被調用的方法 | #root.method.name |
| target | root對象 | 當前被調用的目標對象實例 | #root.target |
| targetClass | root對象 | 當前被調用的目標對象的類 | #root.targetClass |
| args | root對象 | 當前被調用的方法的參數列表 | #root.args[0] |
| caches | root對象 | 當前方法調用使用的緩存列表 | #root.caches[0].name |
| argumentName | 執行上下文(avaluation context) | 當前被調用的方法的參數,如findArtisan(Artisan artisan),可以通過#artsian.id獲得參數 | #artsian.id |
| result | 執行上下文(evaluation context) | 方法執行后的返回值(僅當方法執行后的判斷有效,如 unless cacheEvict的beforeInvocation=false) | #result |
//key = "#username" 就是參數username
@Cacheable(key = "#username" ,cacheNames = "menuCache")
List<Menu> getUserMenus(String username);
keyGenerator
key 的生成器,可以自己指定 key 的生成器,通過這個生成器來生成 key
定義一個@Bean類,將KeyGenerator添加到Spring容器
@Configuration
@Slf4j
@EnableCaching //開啟緩存注解驅動,否則后面使用的緩存都是無效的
public class CacheConfig {
//自定義配置類配置keyGenerator
@Bean("myKeyGenerator")
public KeyGenerator keyGenerator(){
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
return method.getName()+"["+ Arrays.asList(params).toString() +"]";
}
};
}
}
在使用指定自己的@Cacheable(cacheNames = "menuCache",keyGenerator ="myKeyGenerator" )
注意這樣放入緩存中的 key 的生成規則就按照你自定義的 keyGenerator 來生成。不過需要注意的是:@Cacheable 的屬性,key 和 keyGenerator 使用的時候,一般二選一。
condition
符合條件的情況下才緩存。方法返回的數據要不要緩存,可以做一個動態判斷。
/**
* 獲取用戶菜單信息
*
* @param username 用戶名
* @return
*/
//判斷username 用戶名是kenx開頭才會被緩存
@Cacheable(key = "#username" ,condition = "#username.startsWith('kenx')")
List<Menu> getUserMenus(String username);
unless
否定緩存。當 unless 指定的條件為 true ,方法的返回值就不會被緩存。
/**
* 獲取用戶菜單信息
*
* @param username 用戶名
* @return
*/
//判斷username 用戶名是kenx開頭不會被緩存
@Cacheable(key = "#username" ,condition = "#username.startsWith('kenx')")
List<Menu> getUserMenus(String username);
spEL 編寫 key

當然我們可以全局去配置,cacheNames,keyGenerator屬性通過@CacheConfig注解可以用於抽取緩存的公共配置,然后在類加上就可以,eg:如
//全局配置,下面用到緩存方法,不配置默認使用全局的
@CacheConfig(cacheNames = "menuCache",keyGenerator ="myKeyGenerator" )
public interface IMenuService extends IService<Menu> {
/**
* 獲取用戶菜單信息
*
* @param username 用戶名
* @return
*/
@Cacheabl
List<Menu> getUserMenus(String username);
}
深入使用
@CachePut
@CachePut注解也是一個用來緩存的注解,不過緩存和@Cacheable有明顯的區別是即調用方法,又更新緩存數據,也就是執行方法操作之后再來同步更新緩存,所以這個主鍵常用於更新操作,也可以用於查詢,主鍵屬性和@Cacheable有很多類似的參看 @CachePut源碼
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CachePut {
@AliasFor("cacheNames")
String[] value() default {};
@AliasFor("value")
String[] cacheNames() default {};
String key() default "";
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
String condition() default "";
String unless() default "";
}
/**
* @CachePut:既調用方法,又更新緩存數據;同步更新緩存
* 修改了數據,同時更新緩存
*/
@CachePut(value = {"emp"}, key = "#result.id")
public Employee updateEmp(Employee employee){
employeeMapper.updateEmp(employee);
LOG.info("更新{}號員工數據",employee.getId());
return employee;
}
@CacheEvict
清空緩存
主要屬性:
- key:指定要清除的數據
- allEntries = true:指定清除這個緩存中所有的數據
- beforeInvocation = false:默認代表緩存清除操作是在方法執行之后執行
- beforeInvocation = true:代表清除緩存操作是在方法運行之前執行
@CacheEvict(value = {"emp"}, beforeInvocation = true,key="#id")
public void deleteEmp(Integer id){
employeeMapper.deleteEmpById(id);
//int i = 10/0;
}
@Caching
@Caching 用於定義復雜的緩存規則,可以集成@Cacheable和 @CachePut
// @Caching 定義復雜的緩存規則
@Caching(
cacheable = {
@Cacheable(/*value={"emp"},*/key = "#lastName")
},
put = {
@CachePut(/*value={"emp"},*/key = "#result.id"),
@CachePut(/*value={"emp"},*/key = "#result.email")
}
)
public Employee getEmpByLastName(String lastName){
return employeeMapper.getEmpByLastName(lastName);
}
@CacheConfig
@CacheConfig注解可以用於抽取緩存的公共配置,然后在類加上就可以
//全局配置,下面用到緩存方法,不配置默認使用全局的
@CacheConfig(cacheNames = "menuCache",keyGenerator ="myKeyGenerator" )
public interface IMenuService extends IService<Menu> {
/**
* 獲取用戶菜單信息
*
* @param username 用戶名
* @return
*/
@Cacheable(key = "#username" )
List<Menu> getUserMenus(String username);
}
參考
