Spring Cache官网:https://docs.spring.io/spring-framework/docs/5.2.11.RELEASE/spring-framework-reference/integration.html#cache
一、使用
1、引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
2、配置
spring:
# redis配置
redis:
host: localhost
port: 6379
pool:
# 连接池最大连接数(使用负值表示没有限制)
max-active: 8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1
# 连接池中的最大空闲连接
max-idle: 8
# 连接池中的最小空闲连接
min-idle: 0
# 连接超时时间(毫秒)
timeout: 0
# cache配置
cache:
type: redis
3、开启缓存功能
@EnableCaching
@SpringBootApplication
public class GulimallProductApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallProductApplication.class, args);
}
}
4、测试 @Cacheable
@Cacheable(value={"user"},key = "'userList'")
@GetMapping("/getUserList")
public List<User> getUserList(){
System.out.println("getUserList....");
//构造测试数据
List<User> userList = new ArrayList<>();
userList.add(new User("张三", 18));
userList.add(new User("李四", 19));
userList.add(new User("王五", 20));
return userList;
}
@Cacheable 注解说明
缓存当前方法的返回的结果, 如果缓存中有,不调用方法,直接返回;如果缓存中没有,调用方法,最后将方法的结果放入缓存;
默认行为
- 缓存的value值,默认使用jdk序列化机制,将序列化的数据存到redis中;
- key是默认生成的,如果不指定,默认user::SimpleKey [];可以通过key属性指定,接收一个SpEL表达式的值;
- 默认时间是 -1;可以在配置文件中配置
# 缓存存活时间,单位:毫秒 spring.cache.redis.time-to-live=360000
@Cacheable 属性说明
-
value:缓存的分区,指定缓存存储的集合名,可按照业务类型分,同属性 cacheNames;
如果指定缓存前缀
,spring.cache.redis.key-prefix=CACHE_
中的 value会失效!@Cacheable(value={"user"})
-
key:缓存对象存储在Map集合中的key值,缺省按照函数的所有参数组合作为key值,若自己配置需使用SpEL表达式;
例如:
key = "#root.method.name"
表示用方法名作为key
参考:https://docs.spring.io/spring-framework/docs/5.2.11.RELEASE/spring-framework-reference/integration.html#cache-spel-context
5、访问
访问接口:localhost:8080/list
第一次打印
getUserList....
;查看redis缓存数据,redis中已经存入user的数据;
第二次直接走缓存,不调用方法,不打印;
二、自定义缓存配置
缓存中的value默认是用jdk序列化,不方便的我们查看,修改成json序列化;
自定义配置类
@EnableCaching //开启缓存启动类的注解从启动类移到这里
@Configuration
public class MyCacheConfig {
@Bean
public RedisCacheConfiguration redisCacheConfiguration() {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
//key的序列化 string
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
//value的序列化 json
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return config;
}
}
测试(同上),缓存中的value已经用json序列化,但过期时间变成 -1;也就是说配置文件中的值失效了!
[
"java.util.ArrayList",
[
{
"@class": "com.gulimall.product.entity.User",
"name": "张三",
"age": 18
},
{
"@class": "com.gulimall.product.entity.User",
"name": "李四",
"age": 19
},
{
"@class": "com.gulimall.product.entity.User",
"name": "王五",
"age": 20
}
]
]
配置文件失效问题
原因:源码中缓存配置类
CacheProperties
并没有放在容器中我们需要开启属性配置绑定功能:
@EnableConfigurationProperties(CacheProperties.class)
,然后拿到配置文件的值赋值到属性中;可以参考源码
RedisCacheConfiguration
类的
createConfiguration
方法;
@EnableConfigurationProperties(CacheProperties.class) //开启属性配置绑定功能
@EnableCaching //开启缓存启动类的注解从启动类移到这里
@Configuration
public class MyCacheConfig {
@Bean
public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
// config = config.entryTtl();
//key的序列化 string
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
//value的序列化 json
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
//将配置文件中所有的配置都生效
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixKeysWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
}
拿到容器中的
CacheProperties
对象,可以像上面一样通过方法参数拿到,也可以通过注入一个属性拿到;
@Autowired
public CacheProperties cacheProperties;
三、Spring Cache 注解
-
: 将数据保存到缓存;@Cacheable
-
: 将数据从缓存删除;失效模式@CacheEvict
-
: 不影响方法执行更新缓存;双写模式@CachePut
-
: 组合多个缓存操作;@Caching
-
: 在类级别共享缓存的相同配置;@CacheConfig
@Cacheable
添加到缓存
@Cacheable(value={"user"},key = "'userList'")
@GetMapping("/getUserList")
public List<User> getUserList(){
System.out.println("getUserList....");
//构造测试数据
List<User> userList = new ArrayList<>();
userList.add(new User("张三", 18));
userList.add(new User("李四", 19));
userList.add(new User("王五", 20));
return userList;
}
@CacheEvict
删除缓存,【失效模式】
@CacheEvict(value={"user"},key = "'userList'")
@PostMapping("/addUser")
public User addUser(@RequestBody User user){
System.out.println("addUser....");
return user;
}
删除user分区的所有属性:
@CacheEvict(value={"user"}, allEntries = true)
@CachePut
根据返回值更新缓存,【失效模式】
@CachePut(value={"user"}, key = "'userList1'")
@PutMapping("/updateUser")
public List<User> updateUser(){
System.out.println("updateUser....");
//构造测试数据
List<User> userList = new ArrayList<>();
userList.add(new User("1","张三", 18));
return userList;
}
@Caching
组合多个缓存操作;
//同时删除user::userList1 和user::userList2 缓存
@Caching(evict = {
@CacheEvict(value={"user"},key = "'userList1'"),
@CacheEvict(value={"user"},key = "'userList2'")
})
@GetMapping("/caching")
public String caching(){
System.out.println("caching....");
return "caching ok";
}
四、Spring-Cache的不足之处
读模式
-
缓存穿透:查询一个null数据;
解决方案:缓存空数据;
-
缓存击穿:大量并发进来同时查询一个正好过期的数据;
解决方案:默认是无加锁的;使用
来解决击穿问题;@Cacheable(sync = true)
-
缓存雪崩:大量的key同时过期;
解决方案:加过期时间;
读模式的3个问题spring Cache都考虑到了;
写模式:(缓存与数据库一致)
- 读写加锁;
- 引入Canal,感知到MySQL的更新去更新Redis;
- 读多写多,直接去数据库查询就行;
写模式spring Cache 没做特殊处理,根据特殊业务进行特殊处理!
总结
-
常规数据(读多写少,即时性,一致性要求不高的数据,完全可以使用Spring-Cache)
写模式(只要缓存的数据有过期时间就足够了);
- 特殊数据:特殊设计。