@Cacheable、@CacheEvict、@CachePut为Spring自带缓存,可作用在方法和类上。作用在方法上时,只对方法生效,作用在类上时,对类中所有方法生效。
没有集成Redis时,默认缓存为内存。集成Redis后,缓存内容将存入Redis。
SpringBoot项目在pom中引入Redis包并在application配置文件中配置后。在启动类上加上@EnableCaching注解 自动生效,缓存存入Redis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
application.properties
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database=5
#spring.redis.password=123456
# 连接超时时间 单位 ms(毫秒)
spring.redis.timeout=3000
#=========redis线程池设置=========
# 连接池中的最大空闲连接,默认值也是8。
spring.redis.pool.max-idle=10
#连接池中的最小空闲连接,默认值也是0。
spring.redis.pool.min-idle=0
# 如果赋值为-1,则表示不限制;pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。
spring.redis.pool.max-active=10
# 等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时
spring.redis.pool.max-wait=1000
此时我们连续调用三次下面接口
@RequestMapping("/b")
@Cacheable(value = "cache",key = "#id")
public List<String> getPrud(@RequestParam("id") String id){
System.out.println("进入方法");
List<String> list = new ArrayList<>();
list.add(id);
list.add("123");
list.add("456");
return list;
}
发现三次都返回正确结果,但是查看控制台发现只执行了一次接口
这就是因为缓存起到作用了,后面两次发现缓存中有值,直接返回缓存中的结果,不执行方法。
去Redis中查看
发现的确有缓存,但是是乱码。进行如下配置,解决乱码问题
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
private Duration timeToLive = Duration.ofHours(1);
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 使用Jackson2JsonRedisSerialize 替换默认序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// 设置value的序列化规则和 key的序列化规则
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题)
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(timeToLive)
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
问题解决
下面详细说说几个注解的作用
@Cacheable
调用方法前先去缓存中根据key查找是否有缓存,如果有则直接返回缓存结果,不调用方法;如果没有找到则执行方法,并在将方法返回值存入缓存中。
@Cacheable(value = “cache”,key = “#id”) 为例
其中value表示缓存名称,为必填。表示当前方法的返回值会被缓存在哪个Cache上,可以是一个Cache也可以是多个Cache,当需要指定多个Cache时其是一个数组。
key表示缓存的key,有自动生成策略和自定义两种。上面的写法是Spring的EL表达式,最后缓存的key的值就是方法接受参数id的值。
//若user.id=1 key值为 users::user1
@Cacheable(value="users", key="'user'+#user.id")
public User find(User user) {
return null;
}
condition
@RequestMapping("/b")
@Cacheable(value = "cache",key = "#id",condition="#id%2 == 0")
public List<String> getPrud(@RequestParam("id") String id){
System.out.println("进入方法");
List<String> list = new ArrayList<>();
list.add(id);
list.add("123");
list.add("456");
return list;
}
表示表达式 id%2 == 0 为true时才会将方法返回结果进行缓存。
unless
和condition一个作用,以表达式结果判断是否进行缓存
既然 condition 和 unless 都能决定是否进行缓存,那么同时指定这两个参数并且结果相冲突的时候,会怎么样呢?
- condition 不指定相当于 true,unless 不指定相当于 false
- 当 condition = false,一定不会缓存;
- 当 condition = true,且 unless = true,不缓存;
- 当 condition = true,且 unless = false,缓存;
@CachePut
@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
@CacheEvict
@CacheEvict表示在方法执行完成之后,默认执行清除缓存操作。
@CacheEvict可以指定的属性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的语义与@Cacheable对应的属性类似。即value表示清除操作是发生在哪些Cache上的(对应Cache的名称);key表示需要清除的是哪个key,如未指定则会使用默认策略生成的key;condition表示清除操作发生的条件。下面我们来介绍一下新出现的两个属性allEntries和beforeInvocation。
**allEntries**是boolean类型,表示是否需要清除缓存中的所有元素。默认为false,表示不需要。当指定了allEntries为true时,Spring Cache将忽略指定的key。有的时候我们需要Cache一下清除所有的元素,这比一个一个清除元素更有效率。
@CacheEvict(value="users", allEntries=true)
public void delete(Integer id) {
System.out.println("delete user by id: " + id);
}
**beforeInvocation** 清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。使用beforeInvocation可以改变触发清除操作的时间,当我们指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。
@CacheEvict(value="users", beforeInvocation=true)
public void delete(Integer id) {
System.out.println("delete user by id: " + id);
}
@Caching
用于组合@Cacheable、@CacheEvict、@CachePut 三个注解
@Caching(
@CacheEvict(value= {"SubscribeRecordPO_LIST"}, key = "'orgCode'+#po.getTargetOrgCode()"),
@CachePut(value= {"SubscribeRecordPO"}, key = "'macCode'+#po.getMacCode()",unless = "#result == null")
)
public SubscribeRecordPO updateSubscribe(SubscribeRecordPO po) {
//TODO
}
上面的代码表示在方法执行后清除缓存SubscribeRecordPO_LIST中key值为"‘orgCode’+#po.getTargetOrgCode()"的缓存,在方法执行后且返回值不为null时,将返回值缓存进SubscribeRecordPO中。
参考
@Cacheable缓存注解(以Redis作为缓存)
SpringBoot Redis缓存 @Cacheable、@CacheEvict、@CachePut