瑞吉外卖实战项目全攻略——优化篇第一天
该系列将记录一份完整的实战项目的完成过程,该篇属于优化篇第一天,主要负责完成缓存优化问题
案例来自B站黑马程序员Java项目实战《瑞吉外卖》,请结合课程资料阅读以下内容
该篇我们将完成以下内容:
- Git管理
- Redis环境搭建
- 缓存短信验证码
- 缓存菜品数据
- Spring Cache
- 缓存套餐数据
- Git合并管理
Git管理
在之前的文章中我们已经详细介绍Git的相关知识以及使用
下面我们将Git使用在我们的当前项目里方便我们管理和回顾各个版本的流程:
- Gitee码云创建仓库,并复制其SSH

- IDEA项目中创建Git管理机制

- 配置.gitignore文件,控制上传文件的类型
.git logs rebel.xml target/ !.mvn/wrapper/maven-wrapper.jar log.path_IS_UNDEFINED .DS_Store offline_user.md ### STS ### .apt_generated .classpath .factorypath .project .settings .springBeans ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr ### NetBeans ### nbproject/private/ build/ nbbuild/ dist/ nbdist/ .nb-gradle/ generatorConfig.xml ### nacos ### third-party/nacos/derby.log third-party/nacos/data/ third-party/nacos/work/ file/
- 提交当前文件至本地仓库

- 设置远程仓库SSH并提交文件

- 创建一个分支V1.0来供我们开发使用,到后期将该开发分支与主分支合并即可

- 将当前分支传给远程仓库,并使用该分支进行开发

至此我们的Git管理就完成了
Redis环境搭建
我们第一天的优化主要针对于缓存优化,我们采用Redis来进行缓存存储
在正式进行缓存优化之前,我们需要先将Redis的环境搭建完成,下面我们开始进行搭建:
- 导入Redis坐标
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
- 配置yaml配置文件
server: port: 8080 # redis设置在spring下 spring: application: name: qiuluo datasource: druid: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/reggie username: root password: 123456 redis: host: localhost port: 6379 # password: 123456 database: 0 mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: ASSIGN_ID reggie: path: E:编程内容实战项目瑞吉外卖Codereggieimgs
- 配置序列化配置类
// 我们希望在Redis数据库中可以直接查看到key的原始名称,所以我们需要修改其序列化方法 package com.qiuluo.reggie.config; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.StringRedisSerializer; @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; } }
至此我们的Redis环境搭建就完成了
缓存短信验证码
我们的功能开发分为三部分
实现思路
前面我们已经完成了短信验证码的开发功能,但我们的短信验证码是存储在Session中的,有效期只有30s
在学习Redis后,我们可以将短信验证码存储到Redis中并设置相应保存时间,当时间到达或账号登陆后自动删除即可
同时我们的登录功能在获得验证码时,也需要来到Redis数据库中获得验证码并进行比对,比对成功后后删除该验证码即可
代码实现
我们直接在UserController服务层实现即可:
package com.qiuluo.reggie.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.qiuluo.reggie.common.Result; import com.qiuluo.reggie.domain.User; import com.qiuluo.reggie.service.UserService; import com.qiuluo.reggie.utils.SMSUtils; import com.qiuluo.reggie.utils.ValidateCodeUtils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.jws.soap.SOAPBinding; import javax.servlet.http.HttpSession; import java.util.Map; import java.util.concurrent.TimeUnit; @RestController @RequestMapping("/user") @Slf4j public class UserController { @Autowired private UserService userService; @Autowired private RedisTemplate redisTemplate; /** * 发送验证码功能 */ @PostMapping("/sendMsg") public Result<String> sendMsg(@RequestBody User user, HttpSession session){ // 保存手机号 String phone = user.getPhone(); // 判断手机号是否存在并设置内部逻辑 if (phone != null){ // 随机生成四位密码 String code = ValidateCodeUtils.generateValidateCode(4).toString(); // 因为无法申请signName签名,我们直接在后台查看密码 log.info(code); // 我们采用阿里云发送验证码 // SMSUtils.sendMessage("签名","模板",phone,code); // 将数据放在session中待比对(之前我们将验证码存放在session中) // session.setAttribute(phone,code); // 现在我们存放在Redis中(存放五分钟) redisTemplate.opsForValue().set(phone,code,5, TimeUnit.MINUTES); return Result.success("验证码发送成功"); } return Result.success("验证码发送失败"); } /** * 登录功能 */ @PostMapping("/login") public Result<User> login(@RequestBody Map map, HttpSession session){ log.info(map.toString()); // 获得手机号 String phone = map.get("phone").toString(); // 获得验证码 String code = map.get("code").toString(); // 获得Session中的验证码 // String codeInSession = session.getAttribute(phone).toString(); // 获得Redis中的验证码 Object codeInSession = redisTemplate.opsForValue().get(phone); // 进行验证码比对 if (codeInSession != null && codeInSession.equals(code) ){ // 登陆成功 log.info("用户登陆成功"); // 判断是否为新用户,如果是自动注册 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); } // 登陆成功就删除验证码 redisTemplate.delete(phone); session.setAttribute("user",user.getId()); return Result.success(user); } // 比对失败登陆失败 return Result.error("登陆失败"); } }
实际测试
在实际测试中,我们主要分为两部分:
-
开启项目,输入手机号,点击发送验证码,这时我们来到Redis数据库中会发现有一个key为手机号的值,查看内容为密码
-
我们输入密码,进入到程序中,再回到Redis数据库中查看key,会发现当时存放的key消失,数据库又变为空白状态
缓存菜品数据
我们的功能开发分为三部分
实现思路
由于每次查询菜品时我们都需要采用Mysql到后台去查询,当用户增多后,大量的访问导致后台访问速度减慢可能会使系统崩溃
所以我们需要修改之前的菜品查询代码,使其先到Redis数据库中访问数据
如果Redis包含数据,直接访问;如果Redis不包含数据,在Mysql查询后将数据放入Redis保存一定时间
此外,如果我们的菜品进行修改时,为了保证移动端和后台的数据一致,我们需要删除Redis中的缓存使其重新从数据库导入数据
代码实现
我们首先来完成查找菜品的代码修改:
package com.qiuluo.reggie.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.qiuluo.reggie.common.Result; import com.qiuluo.reggie.domain.Category; import com.qiuluo.reggie.domain.Dish; import com.qiuluo.reggie.domain.DishFlavor; import com.qiuluo.reggie.dto.DishDto; import com.qiuluo.reggie.service.impl.CategoryServiceImpl; import com.qiuluo.reggie.service.impl.DishFlavorServiceImpl; import com.qiuluo.reggie.service.impl.DishServiceImpl; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @Slf4j @RestController @RequestMapping("/dish") public class DishController { @Autowired private DishServiceImpl dishService; @Autowired private DishFlavorServiceImpl dishFlavorService; @Autowired private CategoryServiceImpl categoryService; @Autowired private RedisTemplate redisTemplate; /** * 根据id查询菜品 * @param dish * @return */ @GetMapping("/list") public Result<List<DishDto>> list(Dish dish){ // 构造返回类型 List<DishDto> dishDtoList = null; // 动态构造构造Redis的key String key = "dish_" + dish.getCategoryId() + "_" + dish.getStatus(); // 1. 先从Redis中查找是否有菜品缓存 dishDtoList = (List<DishDto>) redisTemplate.opsForValue().get(key); // 2.如果存在,则直接返回即可 if (dishDtoList != null){ return Result.success(dishDtoList); } // 3.如果不存在,采用mysql语法调用获得值 // 提取CategoryID Long id = dish.getCategoryId(); // 判断条件 LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(id != null,Dish::getCategoryId,id); queryWrapper.eq(Dish::getStatus,1); queryWrapper.orderByAsc(Dish::getSort); List<Dish> list = dishService.list(queryWrapper); // 创建返回类型 dishDtoList = list.stream().map((item) -> { // 创建新的返回类型内部 DishDto dishDto = new DishDto(); // 将元素复制过去 BeanUtils.copyProperties(item,dishDto); // 设置CategoryName Long categoryId = item.getCategoryId(); LambdaQueryWrapper<Category> categoryLambdaQueryWrapper = new LambdaQueryWrapper<>(); categoryLambdaQueryWrapper.eq(Category::getId,categoryId); Category category = categoryService.getOne(categoryLambdaQueryWrapper); String categoryName = category.getName(); dishDto.setCategoryName(categoryName); // 设置flavor Long dishId = item.getId(); LambdaQueryWrapper<DishFlavor> lambdaQueryWrapper = new LambdaQueryWrapper(); lambdaQueryWrapper.eq(DishFlavor::getDishId,dishId); List<DishFlavor> dishFlavors = dishFlavorService.list(lambdaQueryWrapper); dishDto.setFlavors(dishFlavors); return dishDto; }).collect(Collectors.toList()); // 4.最后获得成功后,将数据存入redis缓存中 redisTemplate.opsForValue().set(key,dishDtoList,60, TimeUnit.MINUTES); return Result.success(dishDtoList); } }
然后我们再来完成菜品的保存,更新和删除时的消除缓存的操作:
package com.qiuluo.reggie.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.qiuluo.reggie.common.Result; import com.qiuluo.reggie.domain.Category; import com.qiuluo.reggie.domain.Dish; import com.qiuluo.reggie.domain.DishFlavor; import com.qiuluo.reggie.dto.DishDto; import com.qiuluo.reggie.service.impl.CategoryServiceImpl; import com.qiuluo.reggie.service.impl.DishFlavorServiceImpl; import com.qiuluo.reggie.service.impl.DishServiceImpl; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @Slf4j @RestController @RequestMapping("/dish") public class DishController { @Autowired private DishServiceImpl dishService; @Autowired private DishFlavorServiceImpl dishFlavorService; @Autowired private CategoryServiceImpl categoryService; @Autowired private RedisTemplate redisTemplate; /** * 新增菜品 * @param dishDto * @return */ @PostMapping public Result<String> save(@RequestBody DishDto dishDto){ dishService.saveWithFlavor(dishDto); // 全局缓存清理 // Set keys = redisTemplate.keys("dish_*"); // redisTemplate.delete(keys); // 单个清理 String key = "dish_" + dishDto.getCategoryId() + "_1"; redisTemplate.delete(key); return Result.success("新创成功"); } /** * 修改数据 * @param * @return */ @PutMapping public Result<String> update(@RequestBody DishDto dishDto){ dishService.updateWithFlavor(dishDto); log.info("修改完成"); // 全局缓存清理 // Set keys = redisTemplate.keys("dish_*"); // redisTemplate.delete(keys); // 单个清理 String key = "dish_" + dishDto.getCategoryId() + "_1"; redisTemplate.delete(key); return Result.success("修改完成"); } /** * 多个删除 * @param ids * @return */ @DeleteMapping public Result<String> deleteByIds(Long[] ids){ for (Long id:ids ) { dishService.removeById(id); } // 全局缓存清理 // Set keys = redisTemplate.keys("dish_*"); // redisTemplate.delete(keys); // 单个清理 String key = "dish_" + ids + "_1"; redisTemplate.delete(key); return Result.success("删除成功"); } }
实际测试
在实际测试中,我们主要分为两部分:
- 我们进入菜品界面,点击菜品分类后,来到Redis数据库,会发现存在对应的key,里面存储了该套餐的菜品
- 我们来到后台界面,对菜品做一定修改,保存后,来到Redis数据库,会发现对应的分类的key消失
Spring Cache
这一小节我们将会介绍一个方便我们使用缓存的新技术
Spring Cache 介绍
Spring Cache是一个框架,实现了基于注解的缓存功能,只需要简单的加一个注解,就能实现缓存功能
Spring Cache提供了一层抽象,底层可以切换不同的Cache实现,具体是通过CacheManager接口来统一不同的缓存技术
针对于不同的缓存技术需要实现不同的CacheManager:
| CacheManager | 描述 |
|---|---|
| EhCacheCacheManager | 使用EhCache作为缓存技术 |
| GuavaCacheManager | 使用Google的GuavaCache作为缓存技术 |
| RedisCacheManager | 使用Redis作为缓存技术 |
Spring Cache 常用注解
我们来介绍Spring Cache用于缓存的常用的四个注解:
| 注解 | 说明 |
|---|---|
| @EnableCaching | 开启缓存注解功能 |
| @Cacheable | 在方法执行前先查看缓存中是否存有数据,如果有数据直接返回数据;如果没有,调用方法并将返回值存入缓存 |
| @CachePut | 将方法的返回值放到缓存 |
| @CacheEvict | 将一条或多条从缓存中删除 |
在Spring项目中,使用缓存技术只需要导入相关缓存技术的依赖包,并在启动类上加上@EnableCaching开启缓存支持即可
Spring Cache 入门案例
接下来我们通过一个简单的小案例来接触Spring Cache的使用
案例解释
首先我们先来简单介绍一下这个案例的内容:
- 我们项目中包含最基本的domain,mapper,service,serviceImpl,controller,主要对数据表user进行操作
然后我们这里给出User的实现类来简单查看其数据表内容:
package com.itheima.entity; import lombok.Data; import java.io.Serializable; @Data public class User implements Serializable { private static final long serialVersionUID = 1L; private Long id; private String name; private int age; private String address; }
案例准备
我们首先采用系统自带的map缓存来进行基于内存的缓存处理
下面我们完成一些准备工作:
- 导入相关坐标
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.5</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.itheima</groupId> <artifactId>cache_demo</artifactId> <version>1.0-SNAPSHOT</version> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!--spring-boot-starter-web中包含了我们的缓存最基本的依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <scope>compile</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.76</version> </dependency> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.6</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.2</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.23</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.4.5</version> </plugin> </plugins> </build> </project>
- 启动类注解
package com.itheima; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; // @EnableCaching开启缓存注解 @Slf4j @SpringBootApplication @EnableCaching public class CacheDemoApplication { public static void main(String[] args) { SpringApplication.run(CacheDemoApplication.class,args); log.info("项目启动成功..."); } }
案例内容
下面我们来进行服务层核心代码的编写,我们一共会使用三个注解:
package com.itheima.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.itheima.entity.User; import com.itheima.service.UserService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.List; @RestController @RequestMapping("/user") @Slf4j public class UserController { @Autowired private CacheManager cacheManager; @Autowired private UserService userService; /** * 保存数据 * @CachePut将返回值放入缓存 * value:缓存的名字,表示一个类型 * key:缓存的键,里面存储我们的返回值 * key的值:这里采用的动态,用#来表示引用 * key的值:可以引用参数的值#user,也可以以#root.args[n]和#pn来表示参数的第n个值,可以引用返回值#result * @param user * @return */ @PostMapping @CachePut(value = "userCache",key = "#root.args[0]") public User save(User user){ userService.save(user); return user; } /** * 删除数据 * 我们删除数据时,需要将对应的缓存也删除 * @CacheEvict将该key删除 * @param id */ @DeleteMapping("/{id}") @CacheEvict(value = "userCache",key = "#id") public void delete(@PathVariable Long id){ userService.removeById(id); } /** * 更新数据 * 我们更新数据时,需要将对应的缓存也删除 * @CacheEvict将该key删除 * @param user * @return */ @PutMapping @CacheEvict(value = "userCache",key = "#user.id") public User update(User user){ userService.updateById(user); return user; } /** * 获得数据 * @Cacheable先从缓存中货的返回值,若存在直接返回,若不存在,执行方法并将返回值放入缓存 * 其中我们设置了condition表示缓存条件,只有当返回值不为空时我们才将数据缓存 * @param id * @return */ @GetMapping("/{id}") @Cacheable(value = "userCache",key = "#id",condition = "#result != null ") public User getById(@PathVariable Long id){ User user = userService.getById(id); return user; } /** * 获得多个数据 * @Cacheable先从缓存中货的返回值,若存在直接返回,若不存在,执行方法并将返回值放入缓存 * 我们将数据匹配的两个条件作为key,为了方便区分并获得该值 * @param user * @return */ @GetMapping("/list") @Cacheable(value = "userCache",key = "#user.id + '_' + #user.name") 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; } }
案例优化
在上面我们已经了解了四个注解的使用方式,接下来让我们开始Redis缓存:
- 添加对应的注解:
<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>
- 书写yaml配置文件中的Redis配置池
server: port: 8080 spring: application: #应用的名称,可选 name: cache_demo datasource: druid: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/cache_demo username: root password: root redis: host: localhost port: 6379 password: 123456 database: 0 cache: redis: time-to-live: 180000 # 这里设置缓存时间,注意单位是毫秒 mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: ASSIGN_ID
- 在启动类上加上EnableCaching注解,开启缓存注解功能,这里不用修改
package com.itheima; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; @Slf4j @SpringBootApplication @EnableCaching public class CacheDemoApplication { public static void main(String[] args) { SpringApplication.run(CacheDemoApplication.class,args); log.info("项目启动成功..."); } }
- 在Controller方法上添加@Cache相关注解,这里做简单修改(getById修改)
package com.itheima.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.itheima.entity.User; import com.itheima.service.UserService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.List; @RestController @RequestMapping("/user") @Slf4j public class UserController { @Autowired private CacheManager cacheManager; @Autowired private UserService userService; /** * 保存数据 * @CachePut将返回值放入缓存 * value:缓存的名字,表示一个类型 * key:缓存的键,里面存储我们的返回值 * key的值:这里采用的动态,用#来表示引用 * key的值:可以引用参数的值#user,也可以以#root.args[n]和#pn来表示参数的第n个值,可以引用返回值#result * @param user * @return */ @PostMapping @CachePut(value = "userCache",key = "#root.args[0]") public User save(User user){ userService.save(user); return user; } /** * 删除数据 * 我们删除数据时,需要将对应的缓存也删除 * @CacheEvict将该key删除 * @param id */ @DeleteMapping("/{id}") @CacheEvict(value = "userCache",key = "#id") public void delete(@PathVariable Long id){ userService.removeById(id); } /** * 更新数据 * 我们更新数据时,需要将对应的缓存也删除 * @CacheEvict将该key删除 * @param user * @return */ @PutMapping @CacheEvict(value = "userCache",key = "#user.id") public User update(User user){ userService.updateById(user); return user; } /** * 获得数据 * @Cacheable先从缓存中货的返回值,若存在直接返回,若不存在,执行方法并将返回值放入缓存 * redis无法使用condition条件,我们只能更换unless条件,表示满足什么条件时将不存入缓存数据 * @param id * @return */ @GetMapping("/{id}") @Cacheable(value = "userCache",key = "#id",unless = "#result == null ") public User getById(@PathVariable Long id){ User user = userService.getById(id); return user; } /** * 获得多个数据 * @Cacheable先从缓存中货的返回值,若存在直接返回,若不存在,执行方法并将返回值放入缓存 * 我们将数据匹配的两个条件作为key,为了方便区分并获得该值 * @param user * @return */ @GetMapping("/list") @Cacheable(value = "userCache",key = "#user.id + '_' + #user.name") 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; } }
缓存套餐数据
我们的功能开发分为三部分
实现思路
由于每次查询套餐时我们都需要采用Mysql到后台去查询,当用户增多后,大量的访问导致后台访问速度减慢可能会使系统崩溃
我们在前面已经接触了Spring Cache,下面我们将用Spring Cache来实现Redis缓存操作
代码实现
我们通过多步完成Spring Cache操作:
- 导入Spring Cache和Redis相关的Maven坐标:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.5</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.xyl</groupId> <artifactId>mydelivery</artifactId> <version>1.0-SNAPSHOT</version> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!--阿里云短信服务--> <dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-core</artifactId> <version>4.5.16</version> </dependency> <dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-dysmsapi</artifactId> <version>2.1.0</version> </dependency> <!--Redis坐标--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--Cache坐标--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> <version>2.2.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <scope>compile</scope> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.2</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version> </dependency> <!-- 将对象 转化为JSON格式--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.76</version> </dependency> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.6</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.23</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.4.5</version> </plugin> </plugins> </build> </project>
- 在application.yaml中配置缓存数据的过期时间
server: port: 8080 spring: application: name: qiuluo datasource: druid: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true username: root password: 123456 redis: host: localhost port: 6379 # password: 123456 database: 0 cache: redis: time-to-live: 180000 # 注意单位是毫秒 mybatis-plus: configuration: #在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射 map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: ASSIGN_ID reggie: path: E:编程内容实战项目瑞吉外卖Codereggieimgs
- 在启动类上添加@EnableCaching注解,开启缓存注解功能
package com.qiuluo.reggie; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.EnableCaching; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.event.TransactionalEventListener; @Slf4j @SpringBootApplication @ServletComponentScan @EnableCaching public class ReggieApplication { public static void main(String[] args) { SpringApplication.run(ReggieApplication.class,args); log.info("项目成功运行"); } }
- 在SetmealController的list方法上加上@Cacheable注解
package com.qiuluo.reggie.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.api.R; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.qiuluo.reggie.common.Result; import com.qiuluo.reggie.domain.Category; import com.qiuluo.reggie.domain.Dish; import com.qiuluo.reggie.domain.Setmeal; import com.qiuluo.reggie.domain.SetmealDish; import com.qiuluo.reggie.dto.DishDto; import com.qiuluo.reggie.dto.SetmealDto; import com.qiuluo.reggie.service.impl.CategoryServiceImpl; import com.qiuluo.reggie.service.impl.DishServiceImpl; import com.qiuluo.reggie.service.impl.SetmealDishServiceImpl; import com.qiuluo.reggie.service.impl.SetmealServiceImpl; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.stream.Collectors; @Slf4j @RestController @RequestMapping("/setmeal") public class SetmealController { @Autowired private DishServiceImpl dishService; @Autowired private SetmealServiceImpl setmealService; @Autowired private SetmealDishServiceImpl setmealDishService; @Autowired private CategoryServiceImpl categoryService; /** * 根据条件查询套餐数据 * @param setmeal * @return */ @Cacheable(value = "setmealCache",key = "#setmeal.categoryId + '_' + #setmeal.status") @GetMapping("/list") public Result<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 Result.success(list); } }
- 在SetmealController的save,update,delete方法上加上@CacheEvict注解
package com.qiuluo.reggie.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.api.R; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.qiuluo.reggie.common.Result; import com.qiuluo.reggie.domain.Category; import com.qiuluo.reggie.domain.Dish; import com.qiuluo.reggie.domain.Setmeal; import com.qiuluo.reggie.domain.SetmealDish; import com.qiuluo.reggie.dto.DishDto; import com.qiuluo.reggie.dto.SetmealDto; import com.qiuluo.reggie.service.impl.CategoryServiceImpl; import com.qiuluo.reggie.service.impl.DishServiceImpl; import com.qiuluo.reggie.service.impl.SetmealDishServiceImpl; import com.qiuluo.reggie.service.impl.SetmealServiceImpl; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.stream.Collectors; @Slf4j @RestController @RequestMapping("/setmeal") public class SetmealController { @Autowired private DishServiceImpl dishService; @Autowired private SetmealServiceImpl setmealService; @Autowired private SetmealDishServiceImpl setmealDishService; @Autowired private CategoryServiceImpl categoryService; /** * 新增 * @CacheEvict:删除缓存功能,allEntries = true表示删除该value类型的所有缓存 * @param setmealDto * @return */ @CacheEvict(value = "setmealCache",allEntries = true) @PostMapping public Result<String> save(@RequestBody SetmealDto setmealDto){ setmealService.saveWithDish(setmealDto); log.info("套餐新增成功"); return Result.success("新创套餐成功"); } /** * 修改 * @CacheEvict:删除缓存功能,allEntries = true表示删除该value类型的所有缓存 * @param setmealDto * @return */ @PutMapping @CacheEvict(value = "setmealCache",allEntries = true) public Result<String> update(@RequestBody SetmealDto setmealDto){ setmealService.updateById(setmealDto); return Result.success("修改成功"); } /** * 删除 * @CacheEvict:删除缓存功能,allEntries = true表示删除该value类型的所有缓存 * @param ids * @return */ @CacheEvict(value = "setmealCache",allEntries = true) @DeleteMapping public Result<String> delete(@RequestParam List<Long> ids){ setmealService.removeWithDish(ids); return Result.success("删除成功"); } }
实际测试
在实际测试中,我们主要分为两部分:
- 来到移动端页面,点击套餐,查看Redis数据库中是否产生了对应的key以及保存的数据是否为该菜品数据
- 来到后台页面,点击新创,修改或删除套餐,查看数据库中是否所有关于套餐的key值都被删除
Git合并管理
到目前为止我们的缓存的优化已经完整结束,我们需要先将该数据保存到本地仓库并上传至远程仓库V1.0版本
我们的目前存在两个分支,master相当于我们项目的主分支,V1.0相当于我们的开发分支
当我们的开发分支开发完毕并且能够正常运行时,我们就可以将该分支合并到主分支:
- 将当前分支切换到主分支

- 将V1.0分支的资料合并到master分支,等待即可

至此我们的Git合并管理就完成了
结束语
该篇内容到这里就结束了,希望能为你带来帮助~
附录
该文章属于学习内容,具体参考B站黑马程序员的Java项目实战《瑞吉外卖》
这里附上视频链接:项目优化Day1-01-本章内容介绍_哔哩哔哩_bilibili