官方文档
https://docs.spring.io/spring-framework/docs/5.3.25-SNAPSHOT/reference/html/integration.html#cache
版本
该文章基于SpringBoot 2.5.7
编写
引入依赖
1 2 3 4 5 6 7 8 9 10 11
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
<dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> </dependency>
|
如果报错,则引入下面的依赖,删除上面引入的依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <exclusions> <exclusion> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </exclusion> </exclusions> </dependency>
<dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency>
|
配置
Redis配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
| import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.cache.CacheProperties; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@Configuration @EnableCaching @AutoConfigureBefore(RedisAutoConfiguration.class) @EnableConfigurationProperties(CacheProperties.class) public class RedisConfig extends CachingConfigurerSupport {
private StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
private Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
@Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(stringRedisSerializer); redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setConnectionFactory(factory);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
@Bean public CacheManager cacheManager(RedisConnectionFactory factory, CacheProperties cacheProperties) { ObjectMapper om = new ObjectMapper(); om.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); om.setSerializationInclusion(JsonInclude.Include.NON_NULL); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY); jackson2JsonRedisSerializer.setObjectMapper(om);
om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); om.registerModule(new JavaTimeModule());
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofDays(30)) .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(stringRedisSerializer)) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) .computePrefixWith(cacheName -> cacheName + ":") .disableCachingNullValues();
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
if (redisProperties.getTimeToLive() != null) { config = config.entryTtl(redisProperties.getTimeToLive()); } if (redisProperties.getKeyPrefix() != null) { config = config.prefixCacheNameWith(redisProperties.getKeyPrefix()); } if (!redisProperties.isCacheNullValues()) { config = config.disableCachingNullValues(); } if (!redisProperties.isUseKeyPrefix()) { config = config.disableKeyPrefix(); }
return RedisCacheManager.builder(factory) .cacheDefaults(config) .build(); } }
|
yml配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| spring: cache: type: redis redis: use-key-prefix: true cache-null-values: true redis: host: 127.0.0.1 port: 6379 password: 123456 database: 0 jedis: pool: max-active: 100 max-idle: 100 max-wait: 50 min-idle: 10
|
测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
@CacheEvict(cacheNames = "user", allEntries = true) @Override public Boolean delete(List<Long> ids) { return removeByIds(ids); }
@Cacheable(cacheNames = "user", key = "'all1'") @Override public List<UserEntity> selectAll1() { return this.list(); }
@Cacheable(cacheNames = "user", key = "'all2'") @Override public List<UserEntity> selectAll2() { return this.list(); } }
|
SpringCache常用注解
使用org.springframework.cache.annotation
包下面的注解
- @Cacheable:添加缓存
- @CacheEvict:删除缓存
- @CachePut:更新缓存
- @Caching:组合以上
@Cacheable、@CacheEvict、@CachePut
等操作
- @CacheConfig:在类级别共享缓存的相同配置
@Cacheable
- 默认属性
- 缓存中有:获取缓存。
- 缓存中没有:先查询,再缓存,然后返回
- 缓存key:默认自动生成。
缓存名字::SimpleKey []
- 缓存value值,默认使用jdk序列化机制,将序列化的值存入redis中
- 默认过期时间:不过期
- 自定义
- 指定生成的缓存使用的key:
key
属性指定。接收一个SpEL
,如果需要传一个字符串,使用''
包裹【@Cacheable(key = "'all'"
)】
- 指定缓存的存活时间:指定
spring.cache.time-to-live的值,单位为毫秒 1:1000
- 将数据保存为JSON:
指定缓存名称和缓存key
指定生成的缓存使用的key:key
属性指定。接收一个SpEL
,如果需要传一个字符串,使用''
包裹【@Cacheable(key = "'all'"
)】
字符串缓存key
1
| @Cacheable(cacheNames = "user", key = "'all'")
|
SpEL生成缓存key
官方文档:https://docs.spring.io/spring-framework/docs/5.3.25-SNAPSHOT/reference/html/integration.html#cache-spel-context
1
| @Cacheable(cacheNames = "user", key = "#root.methodName")
|
指定缓存时间
指定缓存的存活时间:指定spring.cache.time-to-live的值,单位为毫秒 1:1000
1 2 3 4 5
| spring: cache: type: redis redis: time-to-live: 100000
|
@CacheEvict
删除单个缓存
指定缓存名称和缓存key
1
| @CacheEvict(cacheNames = "user", key = "'all1'")
|
删除多个缓存
使用@Caching
使用@Caching,指定多个@CacheEvict,对缓存一一删除
1 2 3 4 5 6
| @Caching( evict = { @CacheEvict(cacheNames = "user", key = "'all1'"), @CacheEvict(cacheNames = "user", key = "'all2'") } )
|
使用@CacheEvict的allEntries
将allEntries设置为true后,只需要@Cacheable中cacheNames相同的将会被全部删除
1
| @CacheEvict(cacheNames = "user", allEntries = true)
|
SpringCache 原理与不足
原理:CacheManager(RedisCacheManager)——>Cache(RedisCache)——>Cache负责缓存的读写
读模式
- 缓存穿透:查询一个null数据。
- 解决方案:缓存空数据,可通过spring.cache.redis.cache-null-values=true
- 缓存击穿:大量并发进来同时查询一个正好过期的数据。
- 解决方案:加锁 ? 默认是无加锁的;使用sync = true来解决击穿问题
- 缓存雪崩:大量的key同时过期。
- 解决方案:加随机时间。加上过期时间:spring.cache.redis.time-to-live=3600000
写模式
- 读写加锁。
- 引入Canal,感知到MySQL的更新去更新Redis
- 读多写多,直接去数据库查询就行
总结
- 常规数据(读多写少,即时性,一致性要求不高的数据,完全可以使用Spring-Cache);写模式(只要缓存的数据有过期时间就足够了)
- 特殊数据:特殊设计
SpringBoot使用SpringCache进行缓存