十二、Redis
1、Redis 入门
Redis是一个基于内存的key-valule 结构数据库。
特点:基于内存存储,读写性能高
场景:适合存储热点数据(热点商品、资讯、新闻)
Redis安装包分为Windows版和Linux版:
Windows版 下载地址: https://github.com/microsoftarchive/redis/releases
Linux版 下载地址: https://download.redis.io/releases/
Redis操作工具:https://gitee.com/qishibo/AnotherRedisDesktopManager/releases
Redis的Windows版属于绿色软件,直接解压即可使用,解压后目录结构如下:
Redis启动命令(cmd):服务名 配置文件名
redis-server.exe redis.windows.conf
启动客户端(cmd操作,可对其存取数据):
redis-cli.exe
可通过配置文件中参数的设置密码,如:requirepass 123456
2、Redis 数据类型
Redis存储的是key-value结构的数据,其中key是字符串类型,value有5种常用的数据类型。
5种常用数据类型:字符串string、哈希hash、列表list、集合set、有序集合sorted set/ zset
- 字符串(string):普通字符串,Redis中最简单的数据类型。
- 哈希(hash):也叫散列,类似于Java中的HashMap结构。可以用来存对象
- 列表(list):按照插入顺序排序,可以有重复元素,类似于Java中的LinkedList,可以永来存有顺序的数据
- 集合(set):无序集合,没有重复元素,类似于Java中的HashSet,可以进行集合间的计算(交集、并集)
- 有序集合(sorted set / zset):集合中每个元素关联一个分数(score)根据分数升序排序,没有重复元素
3、Redis常用命令
3.1 字符串操作命令
set key value //设置指定key的值(如果存在key,那么就修改value) 如:set name 小明 get key //获取指定key的值 如:get namesetex key seconds value //设置指定key的值,并将key的过期时间设为seconds秒,如:setex code 2 123setnx key value //只有在key不存在时设置key的值(如果存在key不能修改value) setnx sex 男
3.2 哈希操作命令
Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象,常用命令:
hset key field value //将哈希表key中的字段field的值设为value
//如:hset 100 name 小明 hset 100 set 男hget key field //获取存储在哈希表中指定字段的值
//如:hget 100 name 获取属性name的value值:小明hkeys key //获取哈希表中所有字段
//如:hkeys 100 结果为:name、sethvals key //获取哈希表中所有值
//如:hvals 100 结果为:小明、男hdel key field //删除存储在哈希表中的指定字段
//如:hdel 100 sex 删除set属性
3.3 列表操作命令
Redis列表(list)是简单的字符串列表,按照插入顺序排序,常用命令
lpush key value1 [value2] //将一个或多个值插入到列表头部(left左边插入)rpush key value1 [value2] //将一个或多个值插入到列表头部(right右边插入)lrange key start stop //获取列表指定范围内的元素(下标从0开始)
//lrange key 0 -1 可返回列表里的全部数据 rpop key //移除并获取列表最后一个元素lpop key //移除并获取列表最前面一个元素llen key //获取列表长度
3.4 集合操作命令
Redis set是string类型的无序集合。集合成员是唯大-的,集合中不能出现重复的数据,常用命令:
sadd key member1 [member2] //向集合添加一个或多个成员smembers key //返回集合中的所有成员scard key //获取集合的成员数sinter key1 [key2] //返回给定所有集合的交集sunion key1 [key2] //返回所有给定集合的并集srem key member1 [member2] //删除集合中一个或多个成员
3.5 有序集合操作命令
Redis有序集合是string类型元素的集合(zset),且不允许有重复成员。每个元素都会关联一个double类型的分数。通过关联一个double类型的分数来对集合进行排序, 常用命令:
zadd key score1 member1 [score2 member2] //向有序集合添加一个或多个成员zrange key start stop [WITHSCORES] //通过索引区间返回有序集合中指定区间内的成员
//加上WITHSCORES 可以输出对应的member,zrange key 0 -1 输出全部的scorezincrby key increment member //有序集合中对指定成员的分数加上增量ZREM key member [member ...] //移除有序集合中的一个或多个成员
3.6 通用命令
Redis的通用命令是不分数据类型的,都可以使用的命令:
keys pattern //查找所有符合给定模式(pattern)的key(正则表达式)exists key //检查给定key是否存在type key //返回key所储存的值的类型del key1 [key2] //该命令用于在key存在时删除key
4、在java中如何操作Redis
4.1 Redis的java客户端
Redis的Java客户端很多,常用的几种:Jedis(官方推荐的)、Lettuce、Spring Data Redis。
Spring Data Redis是Spring的一部分,对Redis底层开发包进行了高度封装。在Spring项目中,可以使用Spring Data Redis来简化操作。
4.2 Spring Date Redis使用方式
操作步骤:
-
导入Spring Data Redis的maven坐标
<dependency> <groupId>org.springframework.boot</ groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
-
配置Redis数据源
spring:data:redis:host: localhostport: 6379password: 123456
-
编写配置类,创建Redis Template对象
// redis配置类 @Configuration @Slf4j public class RedisConfiguration {// 配置redisTemplate@Beanpublic RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){log.info("redisTemplate初始化中...");RedisTemplate redisTemplate = new RedisTemplate();// 设置redis的连接工厂对象redisTemplate.setConnectionFactory(redisConnectionFactory);// 设置redis字符串类型的key的序列化器redisTemplate.setKeySerializer(new StringRedisSerializer());return redisTemplate;} }
-
通过Redis Template对象操作Redis
@SpringBootTest public class RedisTest {@Autowiredprivate RedisTemplate redisTemplate;@Testvoid testRedis() {//创建一个字符串类型的redis操作对象ValueOperations valueOperations = redisTemplate.opsForValue();// 创建一个哈希类型的redis操作对象HashOperations hashOperations = redisTemplate.opsForHash();// 创建一个列表类型的redis操作对象ListOperations listOperations = redisTemplate.opsForList();//创建一个集合类型的redis操作对象SetOperations setOperations = redisTemplate.opsForSet();//创建一个有序集合类型的redis操作对象ZSetOperations zSetOperations = redisTemplate.opsForZSet();}}
4.2.1 Redis字符存取串类型操作
@Testvoid stringRedis(){/*** String类型Redis:set、get、setnx、setex*///创建一个字符串类型的redis操作对象ValueOperations valueOperations =redisTemplate.opsForValue();// 设置值valueOperations.set("name","yzhy");// 获取值String name =(String) valueOperations.get("name");System.out.println(name);// 设置值并设置过期时间(10分钟)valueOperations.set("code","12345",10, TimeUnit.MINUTES);// 设置值,如果key不存在,则设置值valueOperations.setIfAbsent("code", "4321");}
4.2.2 Redis操作Hash类型存取操作
@Testvoid ridisHash(){/*** Hash类型Redis:hset、hget、hkeys、hvals、hlen、hdel*/// 创建一个哈希类型的redis操作对象HashOperations hashOperations =redisTemplate.opsForHash();// 设置值hashOperations.put("user","name","yhzy");hashOperations.put("user","age","18");hashOperations.put("user","sex","男");//取值String age = (String) hashOperations.get("user","age");System.out.println(age);// 取keys的集合Set keys = hashOperations.keys("user");for (Object key : keys) {System.out.println(key);}//取values的集合List values = hashOperations.values("user");for(Object value : values){System.out.println(value);}// 取hash的长度Long size = hashOperations.size("user");System.out.println(size);// 删除hashOperations.delete("user","age");}
}
4.2.3 Redis操作List数据类型
@Testvoid redisList(){/*** List类型Redis:lpush、rpush、lpop、rpop、lrange、lrem、lset、lindex、llen*/ListOperations listOperations = redisTemplate.opsForList();listOperations.rightPush("list","小明");listOperations.rightPush("list","小梅");listOperations.leftPushAll("list","小红","小刚");String name = (String) listOperations.index("list",0);System.out.println(name);List<String> list =listOperations.range("list",0,-1);for (String s : list) {System.out.println(s);}Long size = listOperations.size("list");System.out.println(size);listOperations.leftPop("list");}
}
4.2.4 Redis操作Set集合数据类型
@Testvoid redisSet(){/*** Set类型Redis:sadd、srem、smembers、scard、sismember、srandmember、sinter、sunion*/// 创建一个集合类型的redis操作对象SetOperations setOper= redisTemplate.opsForSet();setOper.add("fruitName1","苹果","香蕉","橘子");setOper.add("fruitName2","苹果","草莓","葡萄");Set<String> fruitNames= setOper.members("fruitName1");for (String fruitName : fruitNames){System.out.println(fruitName);}System.out.println(setOper.size("fruitName1"));// 判断是否包含System.out.println(setOper.isMember("fruitName1","香蕉"));// 求交集Set<String> fruitIntersect = setOper.intersect("fruitName1","fruitName2");for (String fruit : fruitIntersect){System.out.println(fruit);}System.out.println("=====================");// 求并集Set<String> fruitUnion = setOper.union("fruitName1","fruitName2");for (String fruit : fruitUnion){System.out.println(fruit);}}
4.2.5 Redis有序集合数据类型操作
@Testvoid redisZSet(){/*** ZSet类型Redis:zadd、zrem、zrange、zrevrange、zcard、zscore、zrank、zrevrank、zcount、zrangebyscore、zremrangebyrank、zremrangebyscore*/// 创建一个有序集合类型的redis操作对象ZSetOperations zSetOper= redisTemplate.opsForZSet();zSetOper.add("scors","小明",100);zSetOper.add("scors","小红",90);zSetOper.add("scors","小刚",98);System.out.println(zSetOper.size("scors"));System.out.println(zSetOper.score("scors","小刚"));//默认升序排序Set scors = zSetOper.range("scors",0,-1);for (Object o : scors){System.out.println(o);}System.out.println("======================");// 倒序Set scors1 = zSetOper.reverseRange("scors",0,-1);for (Object o : scors1){System.out.println(o);}// 删除zSetOper.remove("scors","小刚","小红");}
4.2.6 Redis通用命令
@Testvoid redisCommon(){/*** Redis通用操作:keys、etits、type、del*/// 获取所有的key(正则表达式)Set<String> keys = redisTemplate.keys("*");for (String key : keys) {System.out.print(redisTemplate.type(key)+" ");System.out.println(key);}// 判断key是否存在Boolean isExist = redisTemplate.hasKey("name");System.out.println(isExist);// 删除对应keyredisTemplate.delete("name");}
5、场景运用设计
5.1 存储营业状态
外卖商家,右自己的外卖管理端,可以添加菜品对外售卖,售卖前,需要将店铺的营业状态从打样中设置为营业中,其中这个状态,是保存到Redis中的。(0代表:打样中;1代表:营业中)
@RestController
@RequestMapping("/admin/shop")
public class ShopController {private static final String KEY="SHOP_STATUS_KEY";@Autowiredprivate RedisTemplate redisTemplate;/*** 设置店铺状态* @param status* @return*/@Operation(summary = "设置店铺状态")@PutMapping("/{status}")public Result setShopStatus(@PathVariable Integer status) {redisTemplate.opsForValue().set(KEY,status);return Result.success("设置成功",status);}/*** 获取店铺状态* @return*/@Operation(summary = "获取店铺状态")@GetMapping("/status")public Result getShopStatus(){Integer status =(Integer) redisTemplate.opsForValue().get(KEY);return Result.success("获取成功",status);}
}
5.2 存储和清除菜品数据
菜品分类喝套餐分类进行存储,其中分类ID作为存储的key,分类里面包含的数据作为value,来存储。
每当通过菜品分类id或者套餐分类id,来对数据进行查询时,进行先查询Redis里面是否有对应的数据,如果没有才查询数据库,从数据库里面查询出来的结果,再缓存到Redis中。
如果,对菜品,或者套装进行增删改操作时,就会将Redis中对应的一组key进行清除,如
(dish_*、setmeal_*)
//查询数据@RestController("userDishController")
@RequestMapping("/user/dish")
public class DishController {@Autowiredprivate RedisTemplate redisTemplate;@Operation(summary = "根据分类ID菜品列表")@GetMapping("/list")public Result list(Long categoryId){//1、构建KeyString key = "dish_"+categoryId;//2、查询Redis缓存List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key);//3、判断缓存是否存在,存在就获取缓存,并返回if(list!=null && list.size()>0){return Result.success(list);}//4、缓存不存在,查询数据库Dish dish = new Dish();dish.setCategoryId(categoryId);list = dishService.getDishWithFlavorListByCategoryId(dish);//5、将查询结果保存到Redis缓存中(存什么类型的数据,就用什么类型的变量接收)redisTemplate.opsForValue().set(key,list);return Result.success(list);}
}
//清除缓存
@RestController
@RequestMapping("/admin/dish")
@Slf4j
public class DishController {@Autowiredprivate RedisTemplate redisTemplate;/*** 添加菜品* @param dishDto* @return*/@Operation(summary = "添加菜品")@PostMappingpublic Result addDish(@RequestBody DishDTO dishDto) {int insert = dishService.addDish(dishDto);if (insert==0) {return Result.error("添加菜品失败");}//单独清除对应key的缓存String key="dish_"+dishDto.getCategoryId();clearCache(key);return Result.success();}/*** 批量删除菜品* @param ids* @return*/@Operation(summary = "批量删除菜品")@DeleteMappingpublic Result deleteDish(@RequestParam List<Long> ids) {int delete = dishService.deleteBatch(ids);if (delete==0){return Result.error("删除失败");}//清理全部key以dish_开头缓存clearCache("dish_*");return Result.success();}@Operation(summary = "修改菜品")@PutMappingpublic Result updateDishWithFlavor(@RequestBody DishDTO dishDto){log.info("修改菜品:"+dishDto.toString());int updateNum=dishService.updateDishWithFlavor(dishDto);if (updateNum==0){return Result.error("修改失败");}//清理全部key以dish_开头缓存clearCache("dish_*");return Result.success();}/*** 清理缓存* @param pattern*/private void clearCache(String pattern){//1、查询出要清理对应缓存的keys,keys方法的参数可以匹配一个通配符,如:dish_*Set<String> keys = redisTemplate.keys(pattern);//2、清理缓存(可以传入一个集合)redisTemplate.delete(keys);}
}
6、Spring Cache
Spring Cache是一个框架, 实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能。
Spring Cache提供了一层抽象, 底层可以切换不同的缓存实现,例如:EHCache、Caffeine、Redis。
它会自动识别你所使用的缓存,只需要你导入对应缓存的jar包,如果你想使用Redis,做为缓存工具的话,你就导入Redis的jar包;如果你想使用EHCache作为缓存的话,你就导入 EHCache 的jar包。
<!--Spring Cache-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
<version>2.7.3</version>
</dependency>
Spring Cache提供的注解 | 说明 |
---|---|
@EnableCaching | 开启缓存注解功能,通常加再启动类上面 |
@Cacheable | 通常加到方法上,在方法执行前先查询缓存中是否有数据,如果有数据,则直接返回缓存数据;如果没有缓存数据,则调用方法并将方法返回值放到缓存中。 |
@CachePut | 加到方法上,将方法的放回值放到缓存中,只放不取 |
@CacheEvict | 将一条或多条数据从缓存中删除 |
具体的实现思路如下:
- 导入Spring Cache和Redis相关maven坐标。
- 在启动类上加入@EnableCaching注解, 开启缓存注解功能。
- 在用户端接口SetmealController的查询方法上加入@Cacheable注解。
- 在管理端接口Setmel控制器的保存、删除、更新、开始或停止等方法。上加入缓存事件注解。
6.1 @CachePut 注解
加到方法上,将方法的放回值放到缓存中,只放不取;使用步骤,及其注意事项
格式:
@CachePut(cacheName="userCache",key="#user.id")
cacheName:就是Redis(或其他缓存工具)的key的前半部分,可以任意字符串,通常是:类名+Cache
key:就是Redis(或其他缓存工具)的key的后半部分,是动态的,这里使用的是Spring表达式来获取到对象的id属性,作为后半部分。#表示Spring表达式;user表示,形参里的user对象;id表示将数据添加到数据库后,返回到形参中user对象里id属性的值。
完整的key,中间由双冒号分隔如:userCache::1、userCache::2
-
在导入对应jar包后,在启动类上加上注解:@EnableCaching
@SpringBootApplication @EnableCaching //开启缓存注解功能 public class SkyServerApplication {public static void main(String[] args) {SpringApplication.run(SkyServerApplication.class, args);} }
-
Mapper层
@Mapper public interface UserMapper {@Insert("insert into user(name,sex,age) values(#{name},#{sex},#{age})")@Options(useGenteratedKeys=true,keyProperty="id") //插入数据完成后将id返回到对象中void addUser(User user); }
-
Service层不变,以前怎么写现在就怎么写
@Service public UserServiceImpl implements UserService {@AutoWriteprivate UserMapper userMapper;@Overridepublic User addUser(User user){userMapper.addUser(user); return; } }
-
Controller层
将注解加入到Controller成的方法上
@RestController @RequestMapping("/user") public class UserController {@Autowiredprivate UserService userService;/** cacheName:就是Redis(或其他缓存工具)的key的前半部分,可以任意字符串,通常是:类名+Cache key:就是Redis(或其他缓存工具)的key的后半部分,是动态的,这里使用的是Spring表达式来获取到对象的id属性,作为后半部分。#表示Spring表达式;user表示,形参里的user;id表示将数据添加到数据库后,返回到user对象里id属性的值。 完整的key,中间由双冒号分隔如:userCache::1、userCache::2*/@PostMapping@CachePut(cacheName="userCache",key="#user.id") //最常使用//@CachePut(cacheName="userCache",key="#result.id") //表示返回结果中的user对象的id//@CachePut(cacheName="userCache",key="#p0.id") //表示形参中的第一个参数中的id//@CachePut(cacheName="userCache",key="#a0.id") //表示形参中的第一个参数中的idpublic Result adduser(@RequesBody User user){userService.addUser(user)return Result.success(user);}}
6.2 @Cacheable注解
在方法执行前先查询缓存中是否有数据,如果有数据,则直接返回缓存数据;如果没有缓存数据,则调用方法并将方法返回值放到缓存中。
格式:
@CachePut(cacheName="userCache",key="#id")
,key的值要根据形参来变化,保证与存储的key所对应。
- Controller层
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@GetMapping// //如果有缓存就获取对应key的缓存,如果没有就执行方法,获取数据库数据存入缓存@Cacheable(cacheName="userCache",key="id") //生成的key如:userCache::2public Result getUserById(Long id){User user= userService.getUserById(id)return Result.success(user);}}
6.3 @CacheEvict注解
将一条或多条数据从缓存中删除
-
Controller层
@RestController @RequestMapping("/user") public class UserController {@Autowiredprivate UserService userService;/**删除指定key的缓存数据*/@DeleteMapping// 将对应key的缓存进行清除@CacheEvict(cacheName="userCache",key="id") //生成的key如:userCache::2public Result deleteUserById(Long id){User user= userService.deleteUserById(id)return Result.success();}/**以某某key开头的全部缓存数据,这里是以:userCache开头的全部数据*/@DeleteMapping@CacheEvict(cacheName="userCache",allEntries=true) //生成的key如:userCache::*public Result deleteAllUser(){User user= userService.deleteAllUser()return Result.success();}}