可以减少重复代码,使用注解还有一个好处是可拔插,不适用的时候,直接拿掉注解就可以了,不会影响的业务逻辑,更换使用的缓存方式也是,不需要替换缓存的实现方式,只需要把启用缓存注解的配置放到别的类型的缓存中即可。
使用方式
添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency>
设置启用缓存配置的设置
在redis的配置类RedisConfig.java里面添加
@EnableCaching
具体注解
主要的注解是,@Cacheable、@CachePut、@CacheEvict
@Cacheable就是用来替换我们原来的判空,查询缓存并返回的操作;@CachePut则是每次都会执行方法,其实只是有一个存入缓存的操作(只是“put”);@CacheEvict 是用来清空缓存的。
三个注解都可以用到方法和类上面使用,看注解的定义就可以明白
代码实例
方法上加注解(查询方法)
@Cacheable(cacheNames ="product",key = "#productId",condition="true",unless = "#productId.length() < 10") public ProductInfo findOne(String productId) { return repository.findOne(productId); }
@Cacheable 表示方法的返回值将被缓存;若加载类上,则所有方法返回值将被缓存 cacheNames 缓存名称, key 缓存的字典key 支持SpEL表达式 condition 条件表达式 支持SpEL表达式 true 则缓存,默认空缓存 unless 具有一票否决权。若值为true 则不缓存,false则缓存,支持SpEL表达式; 用的多,可以筛选出错误信息不用cache
方法上加注解(保存方法)
@CachePut(cacheNames ="product" , key = "#productInfo.productId") @Override public ProductInfo save(ProductInfo productInfo) { return repository.save(productInfo); }
@CachePut 更新缓存
注意事项:
-
查询的缓存对象和编辑都必须返回同一个对象
-
并且对象都必须实现Serialiable接口
-
两个方法缓存key必须是同一个
清空缓存
@CacheEvict(cacheNames = "selectAllUser",allEntries = true) 全部删除 @CacheEvict(cacheNames="user", key="#id") 根据id单个删除
@CacheEvict 作用和配置方法: 根据一定的条件对缓存进行清空 cacheNames:存储注解方法调用结果的缓存名称。 key:缓存的 key,可以为空(建议指定)。如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合。 cacheManager:指定使用的cacheManager。 condition:缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存。 allEntries:是否清空所有缓存内容。 beforeInvocation:是否在方法执行前就清空。
如何替换缓存错误的处理
如果此时redis宕机了会怎样?
结果是,会直接抛出异常。但是其实我们希望的是,如果redis发生问题了,最终还是希望请求能够进入到数据库当中,那么,怎么实现呢?答案是重写注解支持的errorHandler方法
具体实现
package com.example.config; import com.example.Util.FastJsonSerializerForRedis; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cache.Cache; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.interceptor.CacheErrorHandler; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration @EnableCaching public class RedisConfig extends CachingConfigurerSupport { Logger log= LoggerFactory.getLogger(RedisConfig.class); @Override public CacheErrorHandler errorHandler() { CacheErrorHandler cacheErrorHandler = new CacheErrorHandler() { @Override public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) { RedisException(exception, key); } @Override public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) { RedisException(exception, key); } @Override public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) { RedisException(exception, key); } @Override public void handleCacheClearError(RuntimeException exception, Cache cache) { RedisException(exception, null); } }; return cacheErrorHandler; } protected void RedisException(Exception exception, Object key){ log.error("redis异常:key=[{}], exception={}", key, exception.getMessage()); } }
但是这样做要在application.properties里设置spring.redis.lettuce.pool.max-wait(redis拿不到链接的最长等待时间)这个属性值不能太大,越大请求的接口的时间会越长。