目录
(2)在启动类上家@EnableCaching注解来开启缓存注解功能
引言:本内容与B站黑马的苍穹外卖项目配套,总结一些Redis的知识点以及苍穹外卖项目的代码解析,希望该内容能给你带来帮助......
一.什么是Redis?
Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
与MySQL数据库不同的是,Redis的数据是存在内存中的。它的读写速度非常快,每秒可以处理超过10万次读写操作。因此redis被广泛应用于缓存,另外,Redis也经常用来做分布式锁。除此之外,Redis支持事务、持久化、LUA 脚本、LRU 驱动事件、多种集群方案。
在同一个时间段内,向数据库进行大量操作(例如:添加大量的内容),就会频繁的查询数据库,导致数据库访问压力过大,最终导致系统响应慢、体验差,这个时候我们就需要使用Redis来缓解数据库的压力。
二.Redis的相关知识:
Redis是通过键值对的形式存储数据(key-value)。
key ->字符串
value -> String(字符串) , hash(哈希/散列) , list(有序列表,相当于Java的队列,可重复) , set(集合,无需不可重) , zset(集合,有序不可重)
想要进阶可以查看下面的博客:
下面是Redis的操作过程
优点:
- 加快了响应速度
- 减少了对数据库的读操作,数据库的压力降低
缺点:
-
内存容量相对硬盘小
-
缓存中的数据可能与数据库中数据不一致
-
内存断电就会清空数据,造成数据丢失
三.如何操作Redis?
1,常用命令:
我们先下载Windows版的redis安装包进行安装,然后在cmd命令行中输入redis-server.exe redis.windows.conf,然后在打开新的cmd运行redis-cli.exe -h localhost -p 6379 -a 密码,当Redis服务启动成功后,可通过客户端进行连接。
我们也是有像操作MySQL一样的命令来操作Redis,这里给大家分享Redis图形化工具Another Redis Desktop Manager。
这个常用命令就不过多叙述了...
2.Spring Data Redis
Spring Data Redis ->Redis的Java客户端,以至于我们可以在idea利用Java代码来操作Redis。
(1) pom.xml 配置:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
(2)配置Redis数据源:
在application.yml文件中:
spring:
redis:
host: localhost #ip
port: 6379 #端口号
password: ****** #Redis的密码
database: 0 #指定使用哪个数据源(可不写,但默认是DB0)
(3)编写配置类,创建RedisTemplate对象:
我们使用RedisTemplate这个类对象内封装的方法来操作Redis,所以我们在配置类内创建RedisTemplate并用Bean注解将其交给Spring容器管理。
@Configuration
@Slf4j
public class RedisConfiguration {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
log.info("开始创建Redis模板对象...0");
RedisTemplate redisTemplate = new RedisTemplate();
//设置redis的连接工厂对象
redisTemplate.setConnectionFactory(redisConnectionFactory);
//设置redis key的序列化器,这里反应的是Redis图形化工具上value值的不同
redisTemplate.setKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
}
(4)通过RedisTemplate对象操作Redis:
String类型:redisTemplate.opsForValue()
hash类型:redisTemplate.opsForHash()
list类型:redisTemplate.opsForList()
set类型:redisTemplate.opsForSet()
zset类型:redisTemplate.opsForZSet()
其内部封装有相关操作Redis的方法,可以通过依赖注入的方式调用相关方法。
下面可以看一下测试类操作String类型来理解一下:
@SpringBootTest
public class SpringDataRedisTest {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void testString(){
redisTemplate.opsForValue().set("city","北京");
String city = (String)redisTemplate.opsForValue().get("city");
System.out.println(city);
redisTemplate.opsForValue().set("code","1234",3, TimeUnit.MINUTES);
redisTemplate.opsForValue().setIfAbsent("lock","1");
redisTemplate.opsForValue().setIfAbsent("lock","2");
}
}
根据黑马苍穹外卖的业务代码(查询操作)来具体理解一下:
@RestController("userDishController")
@RequestMapping("/user/dish")
@Slf4j
@Api(tags = "C端-菜品浏览接口")
public class DishController {
@GetMapping("/list")
@ApiOperation("根据分类id查询菜品")
public Result<List<DishVO>> list(Long categoryId) {
//构造redis中的key,规则:dish_分类id
String key = "dish_" + categoryId;
//查询redis中是否存在菜品数据
List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key);
//判断是否存在缓存
if(list != null && list.size() > 0){
return Result.success(list);
}
//如果不存在缓存
Dish dish = new Dish();
dish.setCategoryId(categoryId);
dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品
list = dishService.listWithFlavor(dish);
//将查询到的数据放入redis缓存中
redisTemplate.opsForValue().set(key,list);
return Result.success(list);
}
}
四.SpringCache框架:
1.什么是SpringCache框架?
基于注解的使用来操作Redis的框架。
2.SpringCache框架的相关注解:
- @Cacheable:在方法执行前查看是否有缓存对应的数据,如果有直接返回数据,如果没有调用方法获取数据返回,并缓存起来。
- @CacheEvict:将一条或多条数据从缓存中删除。
- @CachePut:将方法的返回值放到缓存中
- @EnableCaching:开启缓存注解功能
3.SpringCache框架的使用:
(1)pom.xml文件:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
(2)在启动类上家@EnableCaching注解来开启缓存注解功能
@SpringBootApplication
@EnableCaching //开发缓存注解功能
@Slf4j
public class SkyApplication {
public static void main(String[] args) {
SpringApplication.run(SkyApplication.class, args);
log.info("server started");
}
}
(3)三个注解的使用:
我们在介绍这三个注解前需要知道使用注解后存入Redis的key值是什么?
这个时候就需要介绍 cacheNames,key,allEntries:
用@CachePut举例子(共五种表示方法):
①.
@CachePut(cacheNames = "名字" , key = "#形参.形参内属性")
//key的生成为:名字::属性值
eg:
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@Autowired
private UserMapper userMapper;
@PostMapping
@CachePut(cacheNames = "userCache" , key = "#user.id") //key的生成为:userCache::2
public User save(@RequestBody User user){
userMapper.insert(user);
return user;
}
}
②p0代表第一个形参
@CachePut(cacheNames = "名字" , key = "#p0.属性")
//key的生成为:名字::属性值
③a0代表第一个形参
@CachePut(cacheNames = "名字" , key = "#a0.属性")
//key的生成为:名字::属性值
④root.args[0]代表第一个形参
@CachePut(cacheNames = "名字" , key = "#root.args[0].属性")
//key的生成为:名字::属性值
⑤result是代表返回值
@CachePut(cacheNames = "名字" , key = "#result.属性")
//key的生成为:名字::属性值
随后还要注意@Cacheable不能使用result
1.@CachePut:
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@Autowired
private UserMapper userMapper;
@PostMapping
@CachePut(cacheNames = "userCache" , key = "#user.id") //key的生成为:userCache::2
public User save(@RequestBody User user){
userMapper.insert(user);
return user;
}
}
2.@Cacheable:
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@Autowired
private UserMapper userMapper;
@GetMapping
@Cacheable(cacheNames = "userCache" , key = "#id")
public User getById(Long id){
User user = userMapper.getById(id);
return user;
}
}
3.@CacheEvict:
删除一条数据时:
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@Autowired
private UserMapper userMapper;
@DeleteMapping
@CacheEvict(cacheNames = "userCache" , key = "#id")
public void deleteById(Long id){
userMapper.deleteById(id);
}
}
删除多条数据时:allEntries翻译为所有键值对
@RestController
@RequestMapping("/admin")
@Slf4j
public class UserController {
@Autowired
private UserMapper userMapper;
@PutMapping
@ApiOperation("修改套餐")
@CacheEvict(cacheNames = "setmealCache" , allEntries = true)
public Result update(@RequestBody SetmealDTO setmealDTO){
setmealService.update(setmealDTO);
return Result.success();
}
}
五.实战应用:
1.设置店铺状态:
如果我们想设置一个餐厅是营业状态还是打烊,我们可以设置Integer status属性封装状态,并在Redis中创建key-value,之后获取可以在Redis获取。
@RestController("adminShopController")
@RequestMapping("/admin/shop")
@Api(tags = "店铺相关接口")
@Slf4j
public class ShopController {
public static final String KEY = "SHOP_STATUS";
@Autowired
private RedisTemplate redisTemplate;
/**
* 设置店铺的营业状态
* @param status
* @return
*/
@PutMapping("/{status}")
@ApiOperation("设置店铺的营业状态")
public Result setStatus(@PathVariable Integer status){
log.info("设置店铺的营业状态为:{}",status == 1 ? "营业中" : "打烊中");
redisTemplate.opsForValue().set(KEY,status);
return Result.success();
}
/**
* 获取店铺的营业状态
* @return
*/
@GetMapping("/status")
@ApiOperation("获取店铺的营业状态")
public Result<Integer> getStatus(){
Integer status = (Integer)redisTemplate.opsForValue().get(KEY);
log.info("获取店铺的营业状态:{}",status == 1 ? "营业中" : "打烊中");
return Result.success(status);
}
}
2.用户查询菜品的Redis缓存操作:
将查询过的结果缓存,以便于下次直接在Redis中获得缓存来减少数据库的压力
(1)将用户最开始首次进入页面执行的接口设置@Cacheable,能够直接将回调的数据在Redis缓存,以便下一次直接通过注解的作用直接返回数据,而无需调用接口内方法:
@RestController("userSetmealController")
@RequestMapping("/user/setmeal")
@Api(tags = "C端-套餐浏览接口")
public class SetmealController {
@Autowired
private SetmealService setmealService;
/**
* 条件查询
*
* @param categoryId
* @return
*/
@GetMapping("/list")
@ApiOperation("根据分类id查询套餐")
@Cacheable(cacheNames = "setmealCache" , key = "#categoryId") //key为 setmealCache::100
public Result<List<Setmeal>> list(Long categoryId) {
Setmeal setmeal = new Setmeal();
setmeal.setCategoryId(categoryId);
setmeal.setStatus(StatusConstant.ENABLE);
List<Setmeal> list = setmealService.list(setmeal);
return Result.success(list);
}
}
(2)在商家想要进行增删改的操作,以及与菜品可能会出现缓存问题的接口进行清空缓存,以免缓存与更改后的数据不一致造成错误:(方法删除)
下面的代码为对菜品增删改,以及菜品起售停售进行清空缓存,为了节省以及美观,这里封装了cleanCache()方法,随后仅需要传递形参也就是想要删除的缓存的key格式,然后调用方法redisTemplate.keys(key)获得所有该格式下的key并用set集合封装,随后调用delete方法删除所有key就可以达到清空缓存的目的
@RestController
@RequestMapping("/admin/dish")
@Api(tags = "菜品相关接口")
@Slf4j
public class DishController {
@Autowired
private DishService dishService;
@Autowired
private RedisTemplate redisTemplate;
/**
* 封装的清理缓存数据方法
* @param patten
*/
private void cleanCache(String patten){
//清理删除的菜品缓存
Set keys= redisTemplate.keys(patten);//将所有以dish_的格式的key获取出来
redisTemplate.delete(keys);
}
/**
* 新增菜品
* @param dishDTO
* @return
*/
@PostMapping
@ApiOperation("新增菜品")
public Result save(@RequestBody DishDTO dishDTO){
log.info("新增菜品:{}",dishDTO);
dishService.saveWithFlavor(dishDTO);
//清理缓存
String key = "dish_" + dishDTO.getCategoryId();
cleanCache(key);
return Result.success();
}
/**
* 菜品的批量删除
* @param ids
* @return
*/
@DeleteMapping
@ApiOperation("菜品的批量删除")
public Result delete(@RequestParam List<Long> ids){
log.info("菜品的批量删除:{}",ids);
dishService.deleteBatch(ids);
//清理删除的菜品缓存
cleanCache("dish_*");
return Result.success();
}
/**
* 修改菜品
* @param dishDTO
* @return
*/
@PutMapping
@ApiOperation("修改菜品")
public Result update(@RequestBody DishDTO dishDTO){
log.info("修改菜品,{}",dishDTO);
dishService.updateWithFlavor(dishDTO);
//清理缓存数据
//清理删除的菜品缓存
cleanCache("dish_*");
return Result.success();
}
/**
* 菜品起售停售
* @param status
* @param id
* @return
*/
@PostMapping("/status/{status}")
@ApiOperation("菜品起售停售")
public Result<String> startOrStop(@PathVariable Integer status, Long id){
dishService.startOrStop(status,id);
//清理删除的菜品缓存
cleanCache("dish_*");
return Result.success();
}
}
(3)在商家想要进行增删改的操作,以及与套餐可能会出现缓存问题的接口进行清空缓存,以免缓存与更改后的数据不一致造成错误:(注解删除)
跟上面的一样,只不过这次使用我们SpringCache框架的注解来进行清空缓存的操作。
@RestController
@RequestMapping("/admin/setmeal")
@Api(tags = "套餐相关接口")
@Slf4j
public class SetmealController {
@Autowired
private SetmealService setmealService;
/**
* 新增套餐
* @param setmealDTO
* @return
*/
@PostMapping
@ApiOperation("新增套餐")
@CacheEvict(cacheNames = "setmealCache" , key = "#setmealDTO.categoryId") //key为 setmealCache::100
public Result save(@RequestBody SetmealDTO setmealDTO){
setmealService.saveWithDish(setmealDTO);
return Result.success();
}
/**
* 批量删除套餐
* @return
*/
@DeleteMapping
@ApiOperation("批量删除套餐")
@CacheEvict(cacheNames = "setmealCache" , allEntries = true)
public Result delete(@RequestParam List<Long> ids){
setmealService.deleteBatch(ids);
return Result.success();
}
/**
* 修改套餐
* @param setmealDTO
* @return
*/
@PutMapping
@ApiOperation("修改套餐")
@CacheEvict(cacheNames = "setmealCache" , allEntries = true)
public Result update(@RequestBody SetmealDTO setmealDTO){
setmealService.update(setmealDTO);
return Result.success();
}
/**
* 套餐起售停售
* @param status
* @param id
* @return
*/
@PostMapping("/status/{status}")
@ApiOperation("套餐起售停售")
@CacheEvict(cacheNames = "setmealCache" , allEntries = true)
public Result startOrStop(@PathVariable Integer status, Long id) {
setmealService.startOrStop(status, id);
return Result.success();
}
}
好了,Redis的操作大致内容就这些,读完可以了解缓存击穿、缓存穿透、缓存雪崩这三个问题,今天内容就到这里,感谢收看!!!