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

案例来自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. 【Javaweb】implements Serializable是什么意思?反序列化是什么意思?

    为了保证数据传输的可靠 性,常常要implements Serializable,为什么? 对象本质上是虚无缥缈的,只是内存中的一个地址,如果想要让对象持久化,让对象在网络上传输,总不可能传送一个内存 ...

  2. HttpClient报错Timeout waiting for connection from pool

    报错现象 线上项目使用HttpClient请求第三方的HTTP资源,并发量高的时候,日志框报Timeout waiting for connection from pool 客户端的现象是有时正常,有 ...

  3. 【UniApp】-uni-app-全局数据和局部数据

    前言 好,经过上个章节的介绍完毕之后,了解了一下 uni-app-全局样式和局部样式 那么了解完了全局样式和局部样式之后,这篇文章我再来给大家介绍一下 UniApp 中全局数据和局部数据 搭建演示环境 ...

  4. Springboot的Container Images,docker加springboot

    Spring Boot应用程序可以使用Dockerfiles容器化,或者使用Cloud Native Buildpacks来创建优化的docker兼容的容器映像,您可以在任何地方运行. 1. Effi ...

  5. [CF1830D] Mex Tree

    题目描述 You are given a tree with $ n $ nodes. For each node, you either color it in $ 0 $ or $ 1 $ . T ...

  6. 安装NETDATA集群监控面板

    安装NETDATA集群监控面板 介绍 官方链接 演示网页:https://my-netdata.io/ 官方首页:http://netdata.cloud/ 文档地址:http://docs.netd ...

  7. 记录一个MySQL中order by 和 limit 连用导致分页查询不生效的坑

    具体现象和这位同学的一致,具体的解决办法也是参考这位同学的做法 参考文章地址:https://www.cnblogs.com/yuluoxingkong/p/10681583.html

  8. Codeforces #475 div2

    题目链接:http://codeforces.com/contest/964 A题 答案n/2+1: B题 讨论三种情况 c>b c==b c<b C题 数论,逆元+快速幂,但是我一直卡在 ...

  9. 【Dotnet 工具箱】推荐一个 Flutter 和 .NET 开源的实时聊天 APP

    1. 推荐一个 Flutter 和 .NET 开源的实时聊天 APP Heyy-chat-app Heyy-chat-app 是一个开源的聊天应用,使用 Flutter.Asp.Net Core We ...

  10. 记一次 .NET某工控 宇宙射线 导致程序崩溃分析

    一:背景 1. 讲故事 为什么要提 宇宙射线, 太阳耀斑 导致的程序崩溃呢?主要是昨天在知乎上看了这篇文章:莫非我遇到了传说中的bug? ,由于 rip 中的0x41变成了0x61出现了bit位翻转导 ...