小帽外卖
第十二章 缓存优化
- 问题说明
一、环境搭建
1. maven坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2. 配置文件
spring
redis:
host: 172.17.2.94
port: 6379
password: root@123456
database: 0
3. 配置类
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(connectionFactory);
return redisTemplate;
}
}
二、缓存短信验证码
1. 实现思路
- 前面我们已经实现了移动端手机验证码登录,随机生成的验证码我们是保存在HttpSession中的。
- 现在需要改造为将验证码缓存在Redis中,具体的实现思路如下:
1、在服务端UserController中注入RedisTemplate对象,用于操作Redis 2、在服务端UserController的sendMsg方法中,将随机生成的验证码缓存到Redis中,并设置有效期为5分钟 3、在服务端UserController的login方法中,从Redis中获取缓存的验证码,如果登录成功则删除Redis中的验证码
2. 代码改造
- 在UserController中注入RedisTemplate对象,用于操作Redis
@Autowired
private RedisTemplate redisTemplate;
- 在UserController的sendMsg方法中,将生成的验证码保存到Redis
redisTemplate.opsForValue().set(phone, code, 5, TimeUnit.MINUTES);
- 在UserController的login方法中,从Redis中获取生成的验证码,如果登录成功则删除Redis中缓存的验证码
Object codeInSession = redisTemplate.opsForValue().get(phone);
redisTemplate.delete(phone);
return R.success(user);
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@Autowired
private UserService userService;
@Autowired
private RedisTemplate redisTemplate;
@PostMapping("/sendMsg")
public R<String> sendMsg(@RequestBody User user, HttpSession session) {
String phone = user.getPhone();
if(StringUtils.isNotEmpty(phone)) {
String code = ValidateCodeUtils.generateValidateCode(4).toString();
log.info("code={}",code);
redisTemplate.opsForValue().set(phone, code, 5, TimeUnit.MINUTES);
return R.success("手机验证码短信发送成功");
}
return R.error("短信发送失败");
}
@PostMapping("/login")
public R<User> sendMsg(@RequestBody Map map, HttpSession session) {
log.info(map.toString());
String phone = map.get("phone").toString();
String code = map.get("code").toString();
Object codeInSession = redisTemplate.opsForValue().get(phone);
if(codeInSession != null && codeInSession.equals(code)) {
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getPhone, phone);
User user = userService.getOne(queryWrapper);
if(user == null) {
user = new User();
user.setPhone(phone);
user.setStatus(1);
userService.save(user);
}
session.setAttribute("user", user.getId());
redisTemplate.delete(phone);
return R.success(user);
}
return R.error("登录失败");
}
}
三、缓存菜品数据
1. 实现思路
- 前面我们已经实现了移动端菜品查看功能,对应的服务端方法为DishController的list方法,此方法会根据前端提交的查询条件进行数据库查询操作。在高并发的情况下,频繁查询数据库会导致系统性能下降,服务端响应时间增长。现在需要对此方法进行缓存优化,提高系统的性能。
- 具体的实现思路如下:
1、改造DishController的list方法,先从Redis中获取菜品数据,如果有则直接返回,无需查询数据库;如果没有则查询数据库,并将查询到的菜品数据放入Redis。 2、改造DishController的save和update方法,加入清理缓存的逻辑 - 注意事项:
- 在使用缓存过程中,要注意保证数据库中的数据和缓存中的数据一致,如果数据库中的数据发生变化,需要及时清理缓存数据。
2. 代码改造
- 在DishController中注入RedisTemplate对象,用于操作Redis
@Autowired
private RedisTemplate redisTemplate;
@GetMapping("/list")
public R<List<DishDto>> list(Dish dish) {
List<DishDto> dishDtoList = null;
String key = "dish" + dish.getCategoryId() + "_" + dish.getStatus();
dishDtoList= (List<DishDto>) redisTemplate.opsForValue().get(key);
if(dishDtoList != null) {
return R.success(dishDtoList);
}
}
redisTemplate.opsForValue().set(key, dishDtoList,60, TimeUnit.MINUTES);
return R.success(dishDtoList);
- 改造DishController的update方法
@PutMapping
public R<String> update(@RequestBody DishDto dishDto) {
log.info(dishDto.toString());
dishService.updateWithFlavor(dishDto);
String key = "dish_" + dishDto.getCategoryId() + "_1";
redisTemplate.delete(key);
return R.success("新增菜品成功");
}
@PostMapping
public R<String> save(@RequestBody DishDto dishDto) {
log.info(dishDto.toString());
dishService.saveWithFlavor(dishDto);
String key = "dish_" + dishDto.getCategoryId() + "_1";
redisTemplate.delete(key);
return R.success("新增菜品成功");
}
@RestController
@RequestMapping("/dish")
@Slf4j
public class DishController {
@Autowired
private DishService dishService;
@Autowired
private DishFlavorService dishFlavorService;
@Autowired
private CategoryService categoryService;
@Autowired
private RedisTemplate redisTemplate;
@PostMapping
public R<String> save(@RequestBody DishDto dishDto) {
log.info(dishDto.toString());
dishService.saveWithFlavor(dishDto);
String key = "dish_" + dishDto.getCategoryId() + "_1";
redisTemplate.delete(key);
return R.success("新增菜品成功");
}
@GetMapping("/{id}")
public R<DishDto> get(@PathVariable Long id) {
DishDto dishDto = dishService.getByIdWithFlavor(id);
return R.success(dishDto);
}
@PutMapping
public R<String> update(@RequestBody DishDto dishDto) {
log.info(dishDto.toString());
dishService.updateWithFlavor(dishDto);
String key = "dish_" + dishDto.getCategoryId() + "_1";
redisTemplate.delete(key);
return R.success("新增菜品成功");
}
@GetMapping("/list")
public R<List<DishDto>> list(Dish dish) {
List<DishDto> dishDtoList = null;
String key = "dish" + dish.getCategoryId() + "_" + dish.getStatus();
dishDtoList= (List<DishDto>) redisTemplate.opsForValue().get(key);
if(dishDtoList != null) {
return R.success(dishDtoList);
}
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(dish.getCategoryId() != null, Dish::getCategoryId, dish.getCategoryId());
queryWrapper.eq(Dish::getStatus,1);
queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);
List<Dish> list = dishService.list(queryWrapper);
dishDtoList = list.stream().map((item) -> {
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(item, dishDto);
Long categoryId = item.getCategoryId();
Category category = categoryService.getById(categoryId);
if(category != null) {
String categoryName = category.getName();
dishDto.setCategoryName(categoryName);
}
Long dishId = item.getId();
LambdaQueryWrapper<DishFlavor> queryWrapper1 = new LambdaQueryWrapper<>();
queryWrapper1.eq(DishFlavor::getDishId, dishId);
List<DishFlavor> dishFlavorList = dishFlavorService.list(queryWrapper1);
dishDto.setFlavors(dishFlavorList);
return dishDto;
}).collect(Collectors.toList());
redisTemplate.opsForValue().set(key, dishDtoList,60, TimeUnit.MINUTES);
return R.success(dishDtoList);
}
}
四、Spring Cache
1. Spring Cache 介绍
- Spring Cache是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能。
- Spring Cache提供了一层抽象,底层可以切换不同的cache实现。具体就是通过CacheManager接口来统一不同的缓存技术。
- CacheManager是Spring提供的各种缓存技术抽象接口。
- 针对不同的缓存技术需要实现不同的CacheManager:
2. Spring Cache 常用注解
- 在spring boot项目中,使用缓存技术只需在项目中导入相关缓存技术的依赖包,并在启动类上使用@EnableCaching开启缓存支持即可。
- 例如,使用Redis作为缓存技术,只需要导入Spring data Redis的maven坐标即可。
3. Spring Cache 使用方式
- 在Spring Boot项目中使用Spring Cache的操作步骤(使用redis缓存技术):
1、导入maven坐标 spring-boot-starter-data-redis、spring-boot-starter-cache 2、配置application.yml 3、在启动类上加入@EnableCaching注解,开启缓存注解功能 4、在Controller的方法上加入@Cacheable、@CacheEvict等注解,进行缓存操作
4. Spring Cache 上下文数据
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@Autowired
private CacheManager cacheManager;
@Autowired
private UserService userService;
@CachePut(value = "userCache",key = "#user.id")
@PostMapping
public User save(User user){
userService.save(user);
return user;
}
@CacheEvict(value = "userCache",key = "#p0")
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id){
userService.removeById(id);
}
@CacheEvict(value = "userCache",key = "#result.id")
@PutMapping
public User update(User user){
userService.updateById(user);
return user;
}
@Cacheable(value = "userCache",key = "#id",unless = "#result == null")
@GetMapping("/{id}")
public User getById(@PathVariable Long id){
User user = userService.getById(id);
return user;
}
@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;
}
}
@Slf4j
@SpringBootApplication
@EnableCaching
public class CacheDemoApplication {
public static void main(String[] args) {
SpringApplication.run(CacheDemoApplication.class,args);
log.info("项目启动成功...");
}
}
五、缓存套餐数据
1. 实现思路
- 前面我们已经实现了移动端套餐查看功能,对应的服务端方法为SetmealController的list方法,此方法会根据前端提交的查询条件进行数据库查询操作。在高并发的情况下,频繁查询数据库会导致系统性能下降,服务端响应时间增长。现在需要对此方法进行缓存优化,提高系统的性能。
- 具体的实现思路如下:
1、导入Spring Cache和Redis相关maven坐标 2、在application.yml中配置缓存数据的过期时间 3、在启动类上加入@EnableCaching注解,开启缓存注解功能 4、在SetmealController的list方法上加入@Cacheable注解 5、在SetmealController的save和delete方法上加入CacheEvict注解
2. 代码改造
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
spring:
cache:
redis:
time-to-live: 1800000
@Slf4j
@SpringBootApplication
@ServletComponentScan
@EnableTransactionManagement
@EnableCaching
public class LemonApplication {
public static void main(String[] args) {
SpringApplication.run(LemonApplication.class, args);
log.info("项目启动成功...");
}
}
@RestController
@RequestMapping("/setmeal")
@Slf4j
public class SetmealController {
@Autowired
private SetmealService setmealService;
@Autowired
private CategoryService categoryService;
@Autowired
private SetmealDishService setmealDishService;
@PostMapping
@CacheEvict(value = "setmealCache", allEntries = true)
public R<String> save(@RequestBody SetmealDto setmealDto) {
log.info("套餐信息:{}", setmealDto);
setmealService.saveWithDish(setmealDto);
return R.success("新增套餐成功");
}
@DeleteMapping
@CacheEvict(value = "setmealCache", allEntries = true)
public R<String> delete(@RequestParam List<Long> ids) {
log.info("ids:{}", ids);
setmealService.removeWithDish(ids);
return R.success("套餐数据删除成功");
}
@GetMapping("/list")
@Cacheable(value = "setmealCache", key = "#setmeal.categoryId + '_' + #setmeal.status")
public R<List<Setmeal>> list(Setmeal setmeal) {
LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(setmeal.getCategoryId() != null, Setmeal::getCategoryId, setmeal.getCategoryId());
queryWrapper.eq(setmeal.getStatus() != null, Setmeal::getStatus, setmeal.getStatus());
queryWrapper.orderByDesc(Setmeal::getUpdateTime);
List<Setmeal> list = setmealService.list(queryWrapper);
return R.success(list);
}
}
@Data
public class R<T> implements Serializable {
private Integer code;
private String msg;
private T data;
private Map map = new HashMap();
public static <T> R<T> success(T object) {
R<T> r = new R<T>();
r.data = object;
r.code = 1;
return r;
}
public static <T> R<T> error(String msg) {
R r = new R();
r.msg = msg;
r.code = 0;
return r;
}
public R<T> add(String key, Object value) {
this.map.put(key, value);
return this;
}
}
|