该系列将记录一份完整的实战项目的完成过程,该篇属于优化篇第一天,主要负责完成缓存优化问题

案例来自B站黑马程序员Java项目实战《瑞吉外卖》,请结合课程资料阅读以下内容

该篇我们将完成以下内容:

  • Git管理
  • Redis环境搭建
  • 缓存短信验证码
  • 缓存菜品数据
  • Spring Cache
  • 缓存套餐数据
  • Git合并管理

Git管理

在之前的文章中我们已经详细介绍Git的相关知识以及使用

下面我们将Git使用在我们的当前项目里方便我们管理和回顾各个版本的流程:

  1. Gitee码云创建仓库,并复制其SSH

  1. IDEA项目中创建Git管理机制

  1. 配置.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/
  1. 提交当前文件至本地仓库

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

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

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

至此我们的Git管理就完成了

Redis环境搭建

我们第一天的优化主要针对于缓存优化,我们采用Redis来进行缓存存储

在正式进行缓存优化之前,我们需要先将Redis的环境搭建完成,下面我们开始进行搭建:

  1. 导入Redis坐标
        <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  1. 配置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:\编程内容\实战项目\瑞吉外卖\Code\reggie\imgs\
  1. 配置序列化配置类
// 我们希望在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缓存来进行基于内存的缓存处理

下面我们完成一些准备工作:

  1. 导入相关坐标
<?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>
  1. 启动类注解
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缓存:

  1. 添加对应的注解:
        <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>
  1. 书写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
  1. 在启动类上加上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("项目启动成功...");
}
}
  1. 在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操作:

  1. 导入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>
  1. 在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:\编程内容\实战项目\瑞吉外卖\Code\reggie\imgs\
  1. 在启动类上添加@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("项目成功运行");
}
}
  1. 在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);
}
}
  1. 在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相当于我们的开发分支

当我们的开发分支开发完毕并且能够正常运行时,我们就可以将该分支合并到主分支:

  1. 将当前分支切换到主分支

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

至此我们的Git合并管理就完成了

结束语

该篇内容到这里就结束了,希望能为你带来帮助~

SpringBoot 项目实战 | 瑞吉外卖 优化篇 Day01的更多相关文章

  1. 【WEB API项目实战干货系列】- 导航篇(十足干货分享)

    在今天移动互联网的时代,作为攻城师的我们,谁不想着只写一套API就可以让我们的Web, Android APP, IOS APP, iPad APP, Hybired APP, H5 Web共用共同的 ...

  2. Centos8.3、docker部署springboot项目实战记录

    引言    目前k8s很是火热,我也特意买了本书去学习了一下,但是k8s动辄都是成百上千的服务器运维,对只有几台服务器的应用来说使用k8s就有点像大炮打蚊子.只有几台服务器的应用运维使用传统的tomc ...

  3. Vue+SpringBoot项目实战(一) 搭建环境

    GitHub 地址: https://github.com/dongfanger/sprint-backend https://github.com/dongfanger/sprint-fronten ...

  4. 从零开始Vue项目实战(一)-准备篇

    从前参与过一个react项目的代码编写,大神搭建的框架,我主要负责业务逻辑代码编写,现在回想起来似乎又什么都不会,现在为了巩固前端知识,决定用Vue来做这个项目的移动端网站,我本人Vue是从零开始的, ...

  5. 【项目实战】sass使用基础篇(上)

    Sass是一种CSS预处理语言.CSS预处理语言是一种新的专门的编程语言,编译后形成正常的css文件,为css增加一些编程特性,无需考虑浏览器的兼容性(完全兼容css3),让css更加简洁.适应性更强 ...

  6. 数据量大了一定要分表,分库分表组件Sharding-JDBC入门与项目实战

    最近项目中不少表的数据量越来越大,并且导致了一些数据库的性能问题.因此想借助一些分库分表的中间件,实现自动化分库分表实现.调研下来,发现Sharding-JDBC目前成熟度最高并且应用最广的Java分 ...

  7. 15套java架构师、集群、高可用、高可扩展、高性能、高并发、性能优化、Spring boot、Redis、ActiveMQ、Nginx、Mycat、Netty、Jvm大型分布式项目实战视频教程

    * { font-family: "Microsoft YaHei" !important } h1 { color: #FF0 } 15套java架构师.集群.高可用.高可扩展. ...

  8. 15套java互联网架构师、高并发、集群、负载均衡、高可用、数据库设计、缓存、性能优化、大型分布式 项目实战视频教程

    * { font-family: "Microsoft YaHei" !important } h1 { color: #FF0 } 15套java架构师.集群.高可用.高可扩 展 ...

  9. java架构师负载均衡、高并发、nginx优化、tomcat集群、异步性能优化、Dubbo分布式、Redis持久化、ActiveMQ中间件、Netty互联网、spring大型分布式项目实战视频教程百度网盘

    15套Java架构师详情 * { font-family: "Microsoft YaHei" !important } h1 { background-color: #006; ...

  10. 15套java架构师、集群、高可用、高可扩 展、高性能、高并发、性能优化Redis、ActiveMQ、Nginx、Mycat、Netty、Jvm大型分布式项目实战视频教程

    * { font-family: "Microsoft YaHei" !important } h1 { color: #FF0 } 15套java架构师.集群.高可用.高可扩 展 ...

随机推荐

  1. MODBUS转PROFINET网关TS-180 网关连接西门子 PLC 和工业称重仪表

    随着科技的高速发展,工业自动化行业对日益多样的称重需求越来越高,上海某公司在国内的一个 工业自动化项目中,监控中心系统需要远程实时采集工业称重仪表测量的各种称重参数.该系统使用的是 西门子 S7-30 ...

  2. 【Javaweb】servlet一

    什么是servlet 1.servlet是JavaEE规范之一,规范就是接口. 2.servlet是Javaweb三大组件之一.三大组件分别是:servlet程序.filter过滤器.listener ...

  3. 31. 干货系列从零用Rust编写正反向代理,HTTP限流的实现(limit_req)

    wmproxy wmproxy已用Rust实现http/https代理, socks5代理, 反向代理, 静态文件服务器,四层TCP/UDP转发,七层负载均衡,内网穿透,后续将实现websocket代 ...

  4. C++ Qt开发:Qt的安装与配置

    Qt是一种C++编程框架,用于构建图形用户界面(GUI)应用程序和嵌入式系统.Qt由Qt公司(前身为Nokia)开发,提供了一套跨平台的工具和类库,使开发者能够轻松地创建高效.美观.可扩展的应用程序. ...

  5. [gym104542F] Interesting String Problem

    Since you are a good friend of Jaber and Eyad, they are asking for your help to solve this problem. ...

  6. 浅谈android的activity

    说道activity,大家可以说是熟悉的不能再熟悉,首先,先来个镇楼图, 个人觉得谷歌的这张图,比别的什么生命周期图都好;说下各个生命周期注意的: 1:onstart()时,activity可见; 2 ...

  7. MinIO客户端之alias

    MinIO提供了一个命令行程序mc用于协助用户完成日常的维护.管理类工作. 官方资料 mc alias mc alias list mc alias remove mc alias set mc al ...

  8. JavaFx之使用指定字体样式(二十九)

    JavaFx之使用指定字体样式(二十九) javafx use specified font 29 javafx默认的字体样式太丑,可能需要我们自定义字体样式. 之前说好放弃学习javafx,没想到越 ...

  9. 文心一言 VS 讯飞星火 VS chatgpt (176)-- 算法导论13.3 5题

    五.用go语言,考虑一棵用 RB-INSERT 插人 n 个结点而成的红黑树.证明:如果 n>1,则该树至少有一个红结点. 文心一言: 要证明这个问题,我们首先需要理解红黑树的性质.红黑树是一种 ...

  10. 心理健康数据集:mental_health_chatbot_dataset

    一.数据集描述 1.数据集摘要   该数据集包含与心理健康相关的问题和答案的对话对,以单一文本形式呈现.数据集是从流行的医疗博客(如WebMD.Mayo Clinic和HealthLine).在线常见 ...