redis缓存优化—SpringCache
- 1. 问题引入
- 2. SpringCache
- 2.1 注解@EnableCaching
- 2.2 注解@CachePut
- 2.3 注解@CacheEvict
- 2.4 注解@Cacheable
- 2.5 集成redis
- 2.6 序列化异常
1. 问题引入
问题:所有数据从数据库MySQL直接查询,可能会存在问题:
- 频繁访问数据库,
- 数据库访问压力大,系统性能下降。
- 用户体验较差
解决方法:通过Redis缓存技术
- 好处和优势:
- 降低数据库的访问压力;
- 提高系统的访问性能;
- 从而提升用户体验。
缓存优化思路:
- 先查询缓存
- 如果缓存中有数据则会直接返回
- 如果缓存没有数据,则需要查询数据库,再将数据库查询的结果缓存在redis中。
- 如果数据库中的数据发生新增、修改、删除,缓存数据应该清空,保证和数据库保持一致。
- 清空之后,下一次查询数据库,最新的数据就缓存到了redis(走第一步逻辑,最多只查询数据库一次)
2. SpringCache
- Spring Cache是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能,大大简化我们在业务中操作缓存的代码。
- Spring Cache只是提供了一层抽象,底层可以切换不同的cache实现。具体就是通过CacheManager接口来统一不同的缓存技术。CacheManager是Spring提供的各种缓存技术抽象接口。
针对不同的缓存技术需要实现不同的CacheManager:
CacheManager | 描述 |
EhCacheCacheManager | 使用EhCache作为缓存技术 |
GuavaCacheManager | 使用Google的GuavaCache作为缓存技术 |
RedisCacheManager | 使用Redis作为缓存技术 |
2.1 注解@EnableCaching
- @EnableCaching:在引导类上加该注解,就代表当前项目开启缓存注解功能。
2.2 注解@CachePut
- @CachePut:将方法返回值,放入缓存
- 在save方法上加上注解@CachePut
- 当前save方法是用来保存用户信息的,我们希望在该用户信息保存到数据库的同时,也往缓存中缓存一份数据,我们可以在save方法上加上注解 @CachePut
/**
* @CachePut:
* 作用:将方法返回值,存入缓存
* cacheNames:缓存的名称,每个缓存下面可以有很多key
* key:缓存的key
*/
@CachePut(cacheNames = "cacheUser",key = "#user.id")
@PostMapping
public User save(User user){
userService.save(user);
return user;
}
key的写法如下: #user.id : #user指的是方法形参的名称, id指的是user的id属性 , 也就是使用user的id属性作为key ; #user.name: #user指的是方法形参的名称, name指的是user的name属性 ,也就是使用user的name属性作为key ;
注:最终的数据是缓存在ConcurrentHashMap中,那么当我们的服务器重启之后,缓存中的数据就会丢失,使用Redis来缓存就不存在丢失。
2.3 注解@CacheEvict
- @CacheEvict:清理指定缓存
- 在 delete 方法上加注解@CacheEvict
- 当我们在删除数据库user表的数据的时候,我们需要删除缓存中对应的数据,此时就可以使用@CacheEvict注解
/**
* @CacheEvict:
* 作用:清理指定缓存
* cacheNames:缓存的名称,每个缓存下面可以有很多key
* key:缓存的key
*/
@CacheEvict(cacheNames = "cacheUser" ,key = "#id")
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id){
userService.removeById(id);
}
- 在 update 方法上加注解@CacheEvict
- 更新数据后,数据库数据发生了改变,需要将缓存中对应的数据删掉。
@CacheEvict(cacheNames = "cacheUser",key = "#user")
@PutMapping
public User update(User user){
userService.updateById(user);
return user;
}
- 清除某一份缓存下所有的数据,可以指定@CacheEvict中的一个属性 allEnties,将其设置为true即可。
@CacheEvict(value = "cacheUser",allEntries = true) //清除cacheUser名称下,所有的缓存数据
2.4 注解@Cacheable
- @Cacheable:如果缓存有数据,此方法就不再执行,如果没有数据,就先查询数据库,然后自动存入缓存,下次就直接返回。
- 特点:
- 可以标记在方法上,也可以标记在类上。
- 当标记在方法上时表示该方法是支持缓存的,当标记在类上时则表示该类所有的方法都是支持缓存的。
- 对于一个支持缓存的方法,Spring会在其被调用后将其返回值添加缓存,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法。
- Spring在缓存方法的返回值时是以键值对进行缓存的,值就是方法的返回结果,至于键的话,Spring又支持两种策略,默认策略和自定义策略。
- 在getById上加注解@Cacheable
- condition : 表示满足什么条件, 再进行缓存 ;
- unless:满足条件则不缓存
/**
* @Cacheable 在方法执行前spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;没有数据会调用方法并将方法返回值放到缓存中
* cacheNames:缓存的名称,每个缓存下面可以有很多key
* key:缓存的key
* condition:条件,满足条件时才缓存数据
* unless:满足条件则不缓存
*/
//@Cacheable(cacheNames = "cacheUser",key = "#id", unless = "#result == null")满足条件则不缓存
@Cacheable(cacheNames = "cacheUser",key = "#id")
@GetMapping("/{id}")
public User getById(@PathVariable Long id){
User user = userService.getById(id);
return user;
}
- 在list方法上加注解@Cacheable
- 如果list查询两个条件,需要在缓存的key包含两个参数
/**
* 带条件查询
* @param user
* @return
*/
@Cacheable(value = "userCache",key = "#user.id + '_' + #user.name")
@GetMapping("/list")
public List<User> list(User user){
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(user.getId() != null,User::getId,user.getId());
queryWrapper.eq(user.getName() != null,User::getName,user.getName());
List<User> list = userService.list(queryWrapper);
return list;
}
2.5 集成redis
上述默认的使用ConcurrentHashMap做缓存时,服务重启之后,之前缓存的数据就全部丢失了,操作起来并不友好。
- 引入pom依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- yml配置文件
spring:
redis:
host: 127.0.0.1
port: 6379
#password: 123456
database: 0
cache:
redis:
time-to-live: 1800000 #设置缓存过期时间,可选 (单位是ms, 1800s = 30min)
- 小结:
- 注解@Cacheable和@CachePut都可以保存缓存键值对,它们只能运用于有返回值的方法中;
- 删除缓存key的@CacheEvict则可以用在void的方法上,因为它并不需要去保存任何值;
- 对于查询,我们会考虑使用@Cacheable;
- 对于插入和修改,我们会考虑使用@CachePut 或者 @CacheEvict (先删再查 );
- 对于删除,我们会考虑采用使用@CacheEvict。
2.6 序列化异常
@Cacheable 会将方法的返回值缓存在Redis中,而在Redis中存储对象,该对象是需要被序列化的,而对象要想被成功的序列化,就必须得实现Serializable 接口。而当前我们定义的R,并未实现 Serializable 接口。因此R、实现Serializable接口