和所有以梦为马的诗人一样
我选择永恒的事业
我的事业 就是要成为太阳的一生
— — 海子

由于移动端是面向所有的消费者的,请求压力相对比较大,而我们当前所有的数据查询都是从数据库MySQL中直接查询的,那么可能就存在问题: 频繁访问数据库,数据库访问压力大,系统性能下降,用户体验较差

Redis,通过 Redis 来做缓存,从而降低数据库的访问压力,提高系统的访问性能,从而提升用户体验。加入 Redis 做缓存之后,我们在进行数据查询时,就需要先查询缓存,如果缓存中有数据,直接返回,如果缓存中没有数据,则需要查询数据库,再将数据库查询的结果,缓存在redis中。
Gitee项目地址: https://gitee.com/yuan0_0/reggier.git
编辑 pom.xml 文件:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
编辑 src/main/resources/application.yml 文件:
spring:
...
redis:
host: 127.0.0.1
port: 6379
# password: 123456
database: 0
...
编辑 src/main/java/com/reggie/config/RedisConfig.java 配置类:
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
//默认的Key序列化器为:JdkSerializationRedisSerializer
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(connectionFactory);
return redisTemplate;
}
}
百度网盘链接(无提取码): https://pan.baidu.com/s/1BkB9pTqnBYpac047f4fTrA?pwd=java

解压后,在当前目录,打开终端:

# 执行配置文件,开启redis
.\redis-server.exe .\redis.windows.conf
百度网盘链接(无提取码): https://pan.baidu.com/s/1BkB9pTqnBYpac047f4fTrA?pwd=java

安装后,连接本地数据库:

RedisTemplate对象,用于操作 RedissendMsg 方法中,将随机生成的验证码缓存到 Redis 中,并设置有效期为5分钟login 方法中,从 Redis 中获取缓存的验证码,如果登录成功则删除 Redis 中的验证码;编辑 src/main/java/com/reggie/controller/UserController.java 控制类:
...
public R<String> sendMsg(@RequestBody User user, HttpSession session) {
// 需要将生成的验证码保存到 session
// session.setAttribute(phone, code);
// 将生成的验证码缓存到 redis 中, 并设置有效时间
redisTemplate.opsForValue().set(phone, code, 5, TimeUnit.MINUTES);
...
public R<User> login(@RequestBody Map map, HttpSession session) {
// 获取 Session 中保存的短信验证码
// Object codeInSession = session.getAttribute(phone);
// 从 redis 当中获取缓存的验证码
Object codeInSession = redisTemplate.opsForValue().get(phone);
...
// 如果用户登录成功, 删除redis中缓存的验证码
redisTemplate.delete(phone);
return R.success(user);
流程: 登录 => 查看redis缓存 => 登录成功 => 查看redis缓存
登录: http://localhost:8080/front/page/login.html



页面跳转至主页,redis缓存被清除
| 改造的方法 | redis的数据类型 | redis缓存的key | redis缓存的value |
|---|---|---|---|
| list | string | dish_分类Id_状态 , 比如: dish_12323232323_1 | List |
编辑 src/main/java/com/reggie/controller/DishController.java 控制类:
@Autowired
private RedisTemplate redisTemplate;
...
public R<List<DishDto>> list(Dish dish) {
List<DishDto> dishDtoList = null;
// 动态构造key: 设置redis缓存key值(根据不同菜品分类缓存)
String key = "dish_" + dish.getCategoryId() + "_" + dish.getStatus();
// 先从redis中获取缓存数据
dishDtoList = (List<DishDto>) redisTemplate.opsForValue().get(key);
// 如果存在, 直接返回, 无需查询数据库
if(dishDtoList != null) {
return R.success(dishDtoList);
}
...
// 如果不存在, 需要查询数据库, 并将数据缓存到redis中
redisTemplate.opsForValue().set(key, dishDtoList, 60, TimeUnit.MINUTES);
return R.success(dishDtoList);
为了保证数据库中的数据和缓存中的数据一致,如果数据库中的数据发生变化,需要及时清理缓存数据。所以,我们需要在添加菜品、更新菜品时清空缓存数据。
编辑 src/main/java/com/reggie/controller/DishController.java 控制类:
...
public R<String> save(@RequestBody DishDto dishDto) {
// 清理某个菜品下的缓存数据
String key = "dish_" + dishDto.getCategoryId() + "_1";
redisTemplate.delete(key);
...
public R<String> update(@RequestBody DishDto dishDto) {
// 清理所有的菜品缓存数据
// Set keys = redisTemplate.keys("dish_*");
// redisTemplate.delete(keys);
// 清理某个菜品下的缓存数据
String key = "dish_" + dishDto.getCategoryId() + "_1";
redisTemplate.delete(key);
...
目的: Spring Cache 是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能,大大简化我们在业务中操作缓存的代码。
原理: Spring Cache 只是提供了一层抽象,底层可以切换不同的 cache 实现。具体就是通过CacheManager接口来统一不同的缓存技术。 CacheManager 是 Spring 提供的各种缓存技术抽象接口。
分类: 针对不同的缓存技术需要实现不同的 CacheManager:
| CacheManager | 描述 |
|---|---|
| EhCacheCacheManager | 使用EhCache作为缓存技术 |
| GuavaCacheManager | 使用Google的GuavaCache作为缓存技术 |
| RedisCacheManager | 使用Redis作为缓存技术 |
在 SpringCache 中提供了很多缓存操作的注解,常见的是以下的几个:
| 注解 | 说明 |
|---|---|
| @EnableCaching | 开启缓存注解功能 |
| @Cacheable | 在方法执行前spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,调用方法并将方法返回值放到缓存中 |
| @CachePut | 将方法的返回值放到缓存中 |
| @CacheEvict | 将一条或多条数据从缓存中删除 |
在项目中使用,我们会选择使用redis来做缓存,配置如下内容:
编辑 pom.xml 文件:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-cacheartifactId>
dependency>
编辑 src/main/resources/application.yml 文件:
spring:
redis:
...
cache:
redis:
time-to-live: 1800000 #设置缓存过期时间,可选
编辑 src/main/java/com/reggie/ReggieApplication.java 文件:
...
@EnableCaching // 开始Spring cache注解缓存功能
public class ReggieApplication {
...
SetmealController 的 list 方法上加入 @Cacheable 注解SetmealController 的 save 和 delete 方法上加入 CacheEvict 注解在进行套餐数据查询时,我们需要根据分类ID和套餐的状态进行查询,所以我们在缓存数据时,可以将套餐分类ID和套餐状态组合起来作为key,如: 1627182182_1 (1627182182为分类ID,1为状态)。
编辑 src/main/java/com/reggie/controller/SetmealController.java 文件:
...
@Cacheable(value = "setmealCache", key = "#setmeal.categoryId + '_' + #setmeal.status")
public R<List<Setmeal>> list(Setmeal setmeal) {
...
因为 @Cacheable 会将方法的返回值 R 缓存在 Redis 中,而在 Redis 中存储对象,该对象是需要被序列化的,而对象要想被成功的序列化,就必须得实现 Serializable 接口。而当前我们定义的 R ,并未实现 Serializable 接口。所以,需要让 R 实现 Serializable 接口。
编辑 src/main/java/com/reggie/common/R.java 文件:
...
public class R<T> implements Serializable {
...
在我们添加套餐或者删除套餐数据之后,需要清空当前套餐缓存的全部数据。那么 @CacheEvict注解如何清除某一份缓存下所有的数据呢,这里我们可以指定 @CacheEvict 中的一个属性 allEnties ,将其设置为 true 即可。
编辑 src/main/java/com/reggie/controller/SetmealController.java 文件:
...
@CacheEvict(value = "setmealCache", allEntries = true)
public R<String> save(@RequestBody SetmealDto setmealDto) {
...
@CacheEvict(value = "setmealCache", allEntries = true)
public R<String> delete(@RequestParam List<Long> ids) {
...