SpringCache缓存
SpringCache 缓存
一、Redis缓存
在增删改的时候清理缓存,查找的时候添加
1、清理缓存
/**
* 清理缓存数据
* @param pattern
*/
private void cleancache(String pattern){
log.info("clean redis cache");
Set keys = redisTemplate.keys(pattern);
redisTemplate.delete(keys);
}
例如:
/**
* 新增菜品
* @param dishDTO
* @return
*/
@ApiOperation("新增菜品")
@PostMapping
public Result save(@RequestBody DishDTO dishDTO){
log.info("新增菜品:{}",dishDTO);
dishService.saveWithFlavor(dishDTO);
//清理缓存数据
String key = "dish_" + dishDTO.getCategoryId();
cleancache(key);
return Result.success();
}
2、添加缓存
/**
* 根据分类id查询菜品
*
* @param categoryId
* @return
*/
@GetMapping("/list")
@ApiOperation("根据分类id查询菜品")
public Result<List<DishVO>> list(Long categoryId) {
//构造redis中的key 规则: dish_分类id
String key = "dish_" + categoryId;
//1、查询redis中是否存在菜品数据
List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key);
if(list != null && list.size() > 0) {
//2、如果存在,直接返回,无需查询数据库
return Result.success(list);
}
Dish dish = new Dish();
dish.setCategoryId(categoryId);
dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品
//3、如果不存在,查询数据库,将查询到的数据放入redis中
list = dishService.listWithFlavor(dish);
redisTemplate.opsForValue().set(key, list);
return Result.success(list);
}
二、使用SpringCache
它利用了AOP,实现了基于注解的缓存功能,并且进行了合理的抽象,业务代码不用关心底层是使用了什么缓存框架,只需要简单地加一个注解,就能实现缓存功能了。而且Spring Cache也提供了很多默认的配置,用户可以3秒钟就使用上一个很不错的缓存功能。
1、使用方法
1)添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
2)开启缓存
在启动类加上@EnableCaching
注解即可开启使用缓存。
@SpringBootApplication
@EnableCaching
public class CachingApplication {
public static void main(String[] args) {
SpringApplication.run(CachingApplication.class, args);
}
2、常见注释
Spring Cache有几个常用注解,分别为@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig
。除了最后一个CacheConfig
外,其余四个都可以用在类上或者方法级别上,如果用在类上,就是对该类的所有public方法生效
,下面分别介绍一下这几个注解。
@Cacheable
相关信息
@Cacheble
注解表示这个方法有了缓存的功能,方法的返回值会被缓存下来,下一次调用该方法前,会去检查是否缓存中已经有值,如果有就直接返回,不调用方法
。如果没有,就调用方法,然后把结果缓存起来。这个注解一般用在查询方法上。
cacheNames
:用于指定缓存存储的集合名。由于Spring 4中新增了@CacheConfig
,因此在Spring 3
中原本必须有的value
属性,也成为非必需项了key
:和cacheNames
共同组成一个key
,非必需,缺省按照函数的所有参数组合作为key
值,若自己配置需使用SpEL
表达式,比如:@Cacheable(key = "#p0")
:使用函数第一个参数作为缓存的key
值,更多关于SpEL表达式的详细内容可参考官方文档condition
:缓存对象的条件,非必需
,也需使用SpEL
表达式,只有满足表达式条件的内容才会被缓存,比如:@Cacheable(key = "#p0", condition = "#p0.length() < 3")
,表示只有当第一个参数的长度小于3的时候才会被缓存,若做此配置上面的AAA
用户就不会被缓存,读者可自行实验尝试,在函数调用前进行判断,因此result这种spel里面进行判断时,永远为null.
示例:
@GetMapping("/list")
@ApiOperation("根据分类id查询套餐")
@Cacheable(cacheNames = "setmealCache", key = "#categoryId")
public Result<List<Setmeal>> list(Long categoryId) {
Setmeal setmeal = new Setmeal();
setmeal.setCategoryId(categoryId);
setmeal.setStatus(StatusConstant.ENABLE);
List<Setmeal> list = setmealService.list(setmeal);
return Result.success(list);
}
@CachePut
相关信息
@CachePut
的作用 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable
不同的是,它每次都会触发真实方法的调用
@CacheEvict
相关信息
@CachEvict
的作用 主要针对方法配置,能够根据一定的条件对缓存进行清空
一般用在更新或者删除的方法上。
示例:
@PostMapping
@ApiOperation("新增套餐")
@CacheEvict(cacheNames = "setmealCache", key = "#setmealDTO.categoryId")
public Result save(@RequestBody SetmealDTO setmealDTO){
setmealService.save(setmealDTO);
return Result.success();
}
@Caching
相关信息
Java注解的机制决定了,一个方法上只能有一个相同的注解生效。那有时候可能一个方法会操作多个缓存(这个在删除缓存操作中比较常见,在添加操作中不太常见)。
示例:
@Caching(put = {
@CachePut(value = "user", key = "#user.id"),
@CachePut(value = "user", key = "#user.username"),
@CachePut(value = "user", key = "#user.email")
})
public User save(User user) {
}
@CacheConfig
相关信息
前面提到的四个注解,都是Spring Cache
常用的注解。每个注解都有很多可以配置的属性。
但这几个注解通常都是作用在方法上的,而有些配置可能又是一个类通用的,这种情况就可以使用@CacheConfig
了,它是一个类级别的注解,可以在类级别上配置cacheNames、keyGenerator、cacheManager、cacheResolver
等。
例如:
所有的@Cacheable()
里面都有一个value=“xxx”
的属性,这显然如果方法多了,写起来也是挺累的,如果可以一次性声明完 那就省事了, 所以,有了@CacheConfig
这个配置,@CacheConfig is a class-level annotation that allows to share the cache names
,如果你在你的方法写别的名字,那么依然以方法的名字为准。
@CacheConfig是一个类级别的注解。
3、自定义缓存注解
比如之前的那个@Caching
组合,会让方法上的注解显得整个代码比较乱,此时可以使用自定义注解把这些注解组合到一个注解中,如:
@Caching(put = {
@CachePut(value = "user", key = "#user.id"),
@CachePut(value = "user", key = "#user.username"),
@CachePut(value = "user", key = "#user.email")
})
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface UserSaveCache {
}
这样我们在方法上使用如下代码即可,整个代码显得比较干净。
@UserSaveCache
public User save(User user){}
三、SpringCache原理
Spring Cache
使用的是一个叫做CacheInterceptor
的拦截器。我们如果加了缓存相应的注解,就会走到这个拦截器上。这个拦截器继承了CacheAspectSupport
类,会执行这个类的execute
方法,这个方法就是我们要分析的核心方法了。
@Cacheable的sync
private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
if (contexts.isSynchronized()) { //判断是否是同步
CacheOperationContext context = (CacheOperationContext)contexts.get(CacheableOperation.class).iterator().next();
//判断Condition是否满足条件,如果不满足,就执行方法,因此condition是在方法执行前进行判断的
if (!this.isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
return this.invokeOperation(invoker);
}
//尝试获取key
Object key = this.generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
//生成一个cache对象
Cache cache = (Cache)context.getCaches().iterator().next();
try {
return this.wrapCacheValue(method, this.handleSynchronizedGet(invoker, key, cache));
} catch (Cache.ValueRetrievalException var10) {
Cache.ValueRetrievalException ex = var10;
ReflectionUtils.rethrowRuntimeException(ex.getCause());
}
}
this.processCacheEvicts(contexts.get(CacheEvictOperation.class), true, CacheOperationExpressionEvaluator.NO_RESULT);
Cache.ValueWrapper cacheHit = this.findCachedItem(contexts.get(CacheableOperation.class));
List<CachePutRequest> cachePutRequests = new ArrayList();
if (cacheHit == null) {
this.collectPutRequests(contexts.get(CacheableOperation.class), CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
}
Object cacheValue;
Object returnValue;
if (cacheHit != null && !this.hasCachePut(contexts)) {
cacheValue = cacheHit.get();
returnValue = this.wrapCacheValue(method, cacheValue);
} else {
returnValue = this.invokeOperation(invoker);
cacheValue = this.unwrapReturnValue(returnValue);
}
this.collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
Iterator var8 = cachePutRequests.iterator();
while(var8.hasNext()) {
CachePutRequest cachePutRequest = (CachePutRequest)var8.next();
cachePutRequest.apply(cacheValue);
}
this.processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
return returnValue;
}
接着往下看,可以看到我们得到了一个Cache
。这个Cache
是在我们调用CacheAspectSupport
的execute
方法的时候,会new
一个CacheOperationContext
。在这个Context
的构造方法里,会用cacheResolver
去解析注解中的Cache
,生成Cache
对象。
默认的cacheResolver
是SimpleCacheResolver
,它从CacheOperation
中取得配置的cacheNames
,然后用cacheManager
去get
一个Cache
。这里的cacheManager
是用于管理Cache
的一个容器,默认的cacheManager
是ConcurrentMapCacheManager
。听名字就知道是基于ConcurrentMap
来做的了,底层是ConcurrentHashMap
。
那这里的Cache
是什么东西呢?Cache
就对“缓存容器”的一个抽象,包含了缓存会用到的get、put、evict、putIfAbsent等方法。
不同的cacheNames
会对应不同的Cache
对象,比如我们可以在一个方法上定义两个cacheNames
,虽然也可以用value
,它是cacheNames
的别名,但如果有多个配置的时候,更推荐用cacheNames
,因为这样具有更好的可读性。
默认的Cache是ConcurrentMapCache,它也是基于ConcurrentHashMap的。