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

案例来自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. CentOS6-详细启动流程

    CentOS6的启动流程 第一步:硬件启动阶段 本步的流程: 1. 打开电源: 2. POST自检: 3. BIOS逐一排查设备启动顺序,如果是硬盘启动,读取硬盘的MBR的BootLoader.(这里 ...

  2. 🔥🔥Java开发者的Python快速实战指南:探索向量数据库之文本搜索

    前言 如果说Python是跟随我的步伐学习的话,我觉得我在日常开发方面已经没有太大的问题了.然而,由于我没有Python开发经验,我思考着应该写些什么内容.我回想起学习Java时的学习路线,直接操作数 ...

  3. Pycharm 2022 取消双击 shift 搜索框

    Pycharm取消双击shift搜索框 基于PyCharm 2022.3.2 (Professional Edition),旧版本修改方式自行搜索 双击shift弹出搜索框,输入内容double mo ...

  4. Tensorflow2.0使用Resnet18进行数据训练

    在今年的3月7号,谷歌在 Tensorflow Developer Summit 2019 大会上发布 TensorFlow 2.0 Alpha 版,随后又发布了Beta版本. Resnet18结构 ...

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

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

  6. 浅析 ArrayList

    by emanjusaka from https://www.emanjusaka.top/2023/12/java-arrayList 彼岸花开可奈何 本文欢迎分享与聚合,全文转载请留下原文地址. ...

  7. 数字孪生技术与VR技术的结合会为我们带来什么?

    数字孪生技术与虚拟现实(VR)技术的结合为我们打开了全新的可能性和机遇.这个强大的联合为各个领域带来了巨大的影响和创新. 首先,数字孪生技术与VR技术的结合可以为设计和规划过程提供更直观.身临其境的体 ...

  8. Codeforces Round 911 (Div. 2) 总结

    第一次在赛场上敲莫反,还好最后调出来了! A 题意:你在Minecraft里挖了一些一格的坑(同一列),问你用几桶水可以填满它(可以造无限水). 解法:找大于 \(2\) 的连续段,有的话就是两桶,没 ...

  9. ElasticSearch之cat pending tasks API

    命令样例如下: curl -X GET "https://localhost:9200/_cat/pending_tasks?v=true&pretty" --cacert ...

  10. CentOS 7 安装 Python 3.X版本

    由于Centos7默认安装了python2.7.5版本,因此想安装python 3.X版本就需要特殊处理. 详情可以参考南宫羽香的技术博客原文:https://www.cnblogs.com/lclq ...