项目实战-redis

news/2024/12/29 17:55:57/

springboot集成redis

步骤

1、添加Redis依赖项:在项目的pom.xml文件中添加以下依赖项

<!-- spring data redis 依赖 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- commons-pool2 对象池依赖 -->
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId>
</dependency>

2、配置Redis连接属性:在application.yml中添加以下属性:# Redis配置

注意,如果你的redis有密码,还要配置密码!!!

spring:redis:timeout: 10000ms                        # 连接超时时间host: 192.168.10.100                   # Redis服务器地址port: 6379                              # Redis服务器端口database: 0                             # 选择哪个库,默认0库lettuce:pool:max-active: 1024                    # 最大连接数,默认 8max-wait: 10000ms                   # 最大连接阻塞等待时间,单位毫秒,默认 -1max-idle: 200                       # 最大空闲连接,默认 8min-idle: 5                          # 最小空闲连接,默认 0password: 123123# 商品分类列表 Key
goods.category.list.key: goods:category:list:goodsCategoryList

为什么要配置redis-key?
提高代码的可维护性和可配置性,方便应用程序的管理和部署。
将Redis Key的名称配置在yml(或其他配置文件)中,有以下几个好处:

  1. 集中管理:将Redis Key的名称存储在一个配置文件中,可以方便地对Key进行统一的管理和维护。在需要修改Key名称时,只需要修改配置文件中的内容即可,而不需要在应用程序中修改多处代码。
  2. 易于维护:通过配置文件中的Redis Key名称,开发人员可以直观地了解每个Key的含义和作用,从而更容易地进行代码维护和开发。而且,通过配置文件,可以快速地了解应用程序中使用了哪些Redis Key。
  3. 可配置性:将Redis Key的名称配置在一个文件中,可以方便地进行动态配置。这样,在应用程序启动时,可以根据不同的配置文件加载不同的Redis Key,从而实现灵活的应用程序部署和管理。

命名规则:
这个Redis Key是用于存储商品分类列表数据的,格式为goods:category:list:goodsCategoryList
命名空间:对象类型:数据类型:数据名称
Redis Key的设计通常遵循以下几个原则:
5. 命名空间:通过给Key添加一个命名空间,可以避免不同模块或不同功能使用相同的Key造成冲突。在这里,goods就是一个命名空间,表示这个Key是与商品相关的数据。
6. 对象类型:通常,在Key中包含对象类型可以让开发人员快速地了解这个Key存储的是什么类型的数据。在这里,category表示这个Key存储的是商品分类数据。
7. 层级结构:为了避免Key的名称过于冗长,可以考虑将Key分层。在这里,list表示这个Key存储的是列表类型的数据,而goodsCategoryList是实际的Key的名称。通过这种设计方式,可以使Key的名称更加清晰明了,易于开发人员的理解和维护。同时,也方便了对数据进行管理和查询。

3、 创建RedisTemplate Bean:在Spring配置类中创建RedisTemplate Bean,以便在应用程序中使用RedisTemplate。

/*** Redis配置类** @author zhoubin* @since 1.0.0*/
@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String,Object> redisTemplate(LettuceConnectionFactory redisConnectionFactory){RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();//为string类型key设置序列器redisTemplate.setKeySerializer(new StringRedisSerializer());//为string类型value设置序列器redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());//为hash类型key设置序列器redisTemplate.setHashKeySerializer(new StringRedisSerializer());//为hash类型value设置序列器redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());redisTemplate.setConnectionFactory(redisConnectionFactory);return redisTemplate;}
}

这个配置类的作用是为了创建一个RedisTemplate对象并进行相关配置,具体的作用有以下几点:1. 序列化器配置:配置RedisTemplate对象的序列化器,这样在进行Redis数据操作时,可以保证数据的正确序列化和反序列化。这里使用了两种序列化器,StringRedisSerializerGenericJackson2JsonRedisSerializer,分别用于处理Redis Key和Value的序列化。2. 连接工厂配置:设置要使用的Redis客户端连接工厂。这里使用了LettuceConnectionFactory类型的对象,表示使用Lettuce客户端来进行Redis数据操作。3. Bean注解配置:通过@Bean注解声明一个Bean,使该对象可以在应用程序中被使用。
这样编写的好处在于,RedisTemplate是Spring提供的Redis客户端模板,能够方便地进行Redis数据操作。而该配置类提供了一种便捷的方式来创建RedisTemplate实例并进行相关配置,可以使得在应用程序的其他地方直接使用该实例来进行Redis数据操作,简化了代码的编写。同时,通过注入redisConnectionFactory,可以方便地在其他地方进行配置和替换该连接工厂,提高了代码的扩展性。

为什么要进行数据的序列化
在使用Redis存储数据时,需要将数据序列化成二进制格式,以便于Redis服务器进行存储和读取。Redis只能存储二进制数据,不能直接存储Java对象,因此需要将对象序列化成二进制格式,然后再存储到Redis中。数据序列化的主要作用有:1. 压缩数据:将数据序列化成二进制格式可以大幅减少数据大小,从而节省内存和网络带宽。2. 跨平台传输:不同的平台和语言之间,二进制数据是一种通用的传输格式,比如Java对象序列化成二进制格式后,可以在Python、C++等其他语言中进行反序列化。3. 持久化存储:将数据序列化成二进制格式,可以方便地将数据存储到磁盘中,以实现数据持久化。在Redis中,可以使用不同的序列化器来序列化不同类型的数据。
其中,常用的序列化器有:1. StringRedisSerializer:序列化Redis Key的字符串类型。2. JdkSerializationRedisSerializer:使用JDK自带的ObjectInputStreamObjectOutputStream实现的序列化器,适用于Java对象的序列化。3. Jackson2JsonRedisSerializer:使用Jackson库实现的JSON格式数据的序列化和反序列化。4. GenericJackson2JsonRedisSerializer:使用Jackson库实现的JSON格式数据的泛化型序列化和反序列化,支持类型信息。综上所述,数据序列化可以大幅减少数据大小,方便跨平台传输和存储,而使用不同的序列化器,可以适应不同的数据类型和业务需求。

JsonUtils

public class JsonUtil {private static ObjectMapper objectMapper = new ObjectMapper();/*** 将对象转换成json字符串** @param obj* @return*/public static String object2JsonStr(Object obj) {try {return objectMapper.writeValueAsString(obj);} catch (JsonProcessingException e) {//打印异常信息e.printStackTrace();}return null;}/*** 将字符串转换为对象** @param <T> 泛型*/public static <T> T jsonStr2Object(String jsonStr, Class<T> clazz) {try {return objectMapper.readValue(jsonStr.getBytes("UTF-8"), clazz);} catch (JsonParseException e) {e.printStackTrace();} catch (JsonMappingException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}return null;}/*** 将json数据转换成pojo对象list* <p>Title: jsonToList</p>* <p>Description: </p>** @param jsonStr* @param beanType* @return*/public static <T> List<T> jsonToList(String jsonStr, Class<T> beanType) {JavaType javaType = objectMapper.getTypeFactory().constructParametricType(List.class, beanType);try {List<T> list = objectMapper.readValue(jsonStr, javaType);return list;} catch (Exception e) {e.printStackTrace();}return null;}
}

4、使用RedisTemplate:使用@Autowired注释将RedisTemplate注入到您的服务类中

@Service("GoodsCategoryService")
public class GoodsCategoryServiceImpl implements GoodsCategoryService {@Resourceprivate GoodsCategoryMapper goodsCategoryMapper;@AutowiredRedisTemplate<String,String> redisTemplate;@Value("${goods.category.list.key}")private String goodsCategoryListKey;/*** 分类查询商品分类,顶级分类,二级分类,三级分类* @return*/@Overridepublic List<GoodsCategoryVo> selectCategoryListForView() {//opsForValue()获取redis中操作字符串的对象ValueOperations<String, String> valueOpreations = redisTemplate.opsForValue();//查询Redis缓存是否有数据,有数据直接返回,没有数据去数据库查询String gcvListJson = valueOpreations.get(goodsCategoryListKey);if (!StringUtils.isEmpty(gcvListJson)){return JsonUtil.jsonToList(gcvListJson,GoodsCategoryVo.class);}//========================JDK8新特性======================//创建查询对象GoodsCategoryExample example = new GoodsCategoryExample();//查询所有商品分类List<GoodsCategory> list = goodsCategoryMapper.selectByExample(example);//将GoodsCategory对象转成GoodsCategoryVo对象List<GoodsCategoryVo> gcvList = list.stream().map(e -> {GoodsCategoryVo gcv = new GoodsCategoryVo();BeanUtils.copyProperties(e, gcv);return gcv;}).collect(Collectors.toList());//将List<GoodsCategoryVo>转成Map<parentId,List<GoodsCategoryVo>>//按parentId分组,key就是parentId,值就是parentId对应的List<GoodsCategoryVo>Map<Short, List<GoodsCategoryVo>> map =gcvList.stream().collect(Collectors.groupingBy(GoodsCategoryVo::getParentId));//循环,给children赋值gcvList.forEach(e->e.setChildren(map.get(e.getId())));//拦截器,返回level为1的list,也就是顶级分类List<GoodsCategoryVo> gcvList01 = gcvList.stream().filter(e -> 1 == e.getLevel()).collect(Collectors.toList());//放入Redis缓存valueOpreations.set(goodsCategoryListKey, JsonUtil.object2JsonStr(gcvList01));//========================JDK8新特性=======================return gcvList01;}

补充知识点:java8新特性

在没有使用java8新特性的时候我们是这样写的

List<GoodsCategoryVo> gcvList1 = new ArrayList<>();GoodsCategoryExample example = new GoodsCategoryExample();example.createCriteria().andParentIdEqualTo(((short) 0)).andLevelEqualTo(((byte) 1));List<GoodsCategory> gcList1 = goodsCategoryMapper.selectByExample(example);List<GoodsCategoryVo> gcvList2 = new ArrayList<>();for(GoodsCategory gc1:gcList1){GoodsCategoryVo gcv1 = new GoodsCategoryVo();BeanUtils.copyProperties(gc1,gcv1);example.clear();example.createCriteria().andParentIdEqualTo(gc1.getId()).andLevelEqualTo(((byte) 2));List<GoodsCategory> gcList2 = goodsCategoryMapper.selectByExample(example);List<GoodsCategoryVo> gcvList3 =new ArrayList<>();for(GoodsCategory gc2:gcList2){GoodsCategoryVo gcv2 = new GoodsCategoryVo();BeanUtils.copyProperties(gc2,gcv2);example.clear();example.createCriteria().andParentIdEqualTo(gc2.getId()).andLevelEqualTo(((byte) 3));List<GoodsCategory> gcList3 = goodsCategoryMapper.selectByExample(example);for(GoodsCategory gc3:gcList3){GoodsCategoryVo gcv3 = new GoodsCategoryVo();BeanUtils.copyProperties(gc3,gcv3);gcv3.setChildren(null);gcvList3.add(gcv3);}gcv2.setChildren(gcvList3);gcvList2.add(gcv2);}gcv1.setChildren(gcvList2);gcvList1.add(gcv1);}

使用java8新特性

(1)//将GoodsCategory对象转成GoodsCategoryVo对象
可以选择获取List集合中的每一个GoodsCategory对象,然后将它映射成为GoodsCategoryVo对象

?为什么不直接使用list.stream()。而是使用list.stream().map呢?

因为list.stream()返回的是一个Stream对象,它并没有对原有的元素类型进行转换。而list.stream().map()方法可以对流中的元素进行转换,并返回一个新的Stream` 对象,该对象包含转换后的元素。

list.stream()返回一个顺序流,并且它能让我们对这个序列进行一些操作,例如过滤,映射,排序等等。list.stream().map()` 可以将顺序流中的每个元素通过传入的 Lambda 表达式进行转换,并返回一个新的 Stream 对象,该对象包含转换后的元素。相当于对流中的每个元素进行映射。
因此我们使用list.stream().map((参数)->{转换的语句,return 新元素类型})

?collect的作用是什么?

collect:将流转换为其他形式。接收一个Collector接口的实现。
综上,我们可以根据java8新特性来写转换。
首先获取gcList

List<GoodsCategory> list = goodsCategoryMapper.selectByExample(example);
//将GoodsCategory对象转成GoodsCategoryVo对象
List<GoodsCategoryVo> gcvList = list.stream().map(e -> {
GoodsCategoryVo gcv = new GoodsCategoryVo();BeanUtils.copyProperties(e, gcv);return gcv;}).collect(Collectors.toList());

(2)//根据parenId分组
已知我们分组分成三大类分别是顶级分类,二级分类,三级分类。他们是根据parentId,进行分类的,我们刚好也需要这些分类,因此我们可以根据parentId来讲大分类分成三组,因此我们最终会得到一个Map集合,他的key是parentId,他的value是List<GoodsCategoryVo>
分类用到的stream().collector(Collectors.groupingBy());

	Map<Short, List<GoodsCategoryVo>> map =gcvList.stream().collect(Collectors.groupingBy(GoodsCategoryVo::getParentId));

GoodsCategoryVo::getParentId使用的是 Java 8 中的方法引用(Method Reference)语法。方法引用是一种更简洁的 lambda 表达式的语法糖,用于直接引用已有方法或构造函数,从而在函数式编程中更容易地实现对象的方法传递。在这里,GoodsCategoryVo::getParentId,表示将 GoodsCategoryVo 中的 getParentId 方法作为参数传递给 groupingBy() 方法,用于指定根据哪个属性进行分组。等价于 lambda 表达式 (gcv) -> gcv.getParentId()

**(3)**给每一个Vo对象的children赋值
根据他的父id和本身的id,进行对比,如果一个对象a的父id是对象b的id,那么a就是b的child
通过for循环,将每一个满足条件的元素都进行赋值
list.forEach 是 Java 8 中的新特性,用于在集合中遍历元素,对集合中的每个元素都执行指定操作。它使用了函数式编程中的 lambda 表达式,可以代替传统的 for 循环来遍历集合。

//循环,给children赋值gcvList.forEach(e->e.setChildren(map.get(e.getId())));//拦截器,返回level为1的list,也就是顶级分类List<GoodsCategoryVo> gcvList01 = gcvList.stream().filter(e -> 1 == e.getLevel()).collect(Collectors.toList());

有动态变化的属性在redis缓存中分页查询商品列表

	/*** 商品列表-分页查询* @param goods* @param pageNum* @param pageSize* @return*/@Overridepublic BaseResult selectGoodsListByPage(Goods goods, Integer pageNum, Integer pageSize) {/*** 商品列表RedisKey*  1.无条件查询*      goods:pageNum_:PageSize_:catId_:brandId_:goodsName_:*  2.条件查询*      goods:pageNum_:PageSize_:catId_123:brandId_:goodsName_:*      goods:pageNum_:PageSize_:catId_:brandId_123:goodsName_:*      goods:pageNum_:PageSize_:catId_:brandId_:goodsName_华为:*      goods:pageNum_:PageSize_:catId_123:brandId_123:goodsName_:*      goods:pageNum_:PageSize_:catId_123:brandId_:goodsName_华为:*      goods:pageNum_:PageSize_:catId_:brandId_123:goodsName_华为:*      goods:pageNum_:PageSize_:catId_123:brandId_123:goodsName_华为:*///定义RedisKey数组String[] goodsKeyArr = new String[]{"goods:pageNum_"+pageNum+":PageSize_"+pageSize+":","catId_:","brandId_:","goodsName_:"};//构建分页对象PageHelper.startPage(pageNum,pageSize);//创建查询对象GoodsExample example = new GoodsExample();GoodsExample.Criteria criteria = example.createCriteria();//设置查询条件//分类参数if (null!=goods.getCatId()&&0!=goods.getCatId()){criteria.andCatIdEqualTo(goods.getCatId());goodsKeyArr[1] = "catId_"+goods.getCatId()+":";}//品牌参数if (null!=goods.getBrandId()&&0!=goods.getBrandId()){criteria.andBrandIdEqualTo(goods.getBrandId());goodsKeyArr[2] = "brandtId_"+goods.getBrandId()+":";}//关键词if (!StringUtils.isEmpty(goods.getGoodsName())){criteria.andGoodsNameLike("%"+goods.getGoodsName()+"%");goodsKeyArr[3] = "goodsName_"+goods.getGoodsName()+":";}//拼接完整的RedisKeyString goodsListKey = Arrays.stream(goodsKeyArr).collect(Collectors.joining());ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();//查询缓存,如果缓存中存在数据,直接返回String pageInfoGoodsJson = valueOperations.get(goodsListKey);if (!StringUtils.isEmpty(pageInfoGoodsJson)){return BaseResult.success(JsonUtil.jsonStr2Object(pageInfoGoodsJson,PageInfo.class));}//==============错误代码==================// String listGoodsJson = valueOperations.get(goodsListKey);// if (!StringUtils.isEmpty(listGoodsJson)){// 	List<Goods> goodsList = JsonUtil.jsonToList(listGoodsJson, Goods.class);// 	PageInfo<Goods> pageInfo = new PageInfo<>(goodsList);// 	return BaseResult.success(pageInfo);// }//==============错误代码==================//判断查询结果是否为空,不为空放入分页对象List<Goods> list = goodsMapper.selectByExample(example);if (!CollectionUtils.isEmpty(list)){PageInfo<Goods> pageInfo = new PageInfo<>(list);//放入缓存valueOperations.set(goodsListKey,JsonUtil.object2JsonStr(pageInfo));//==============错误代码==================// valueOperations.set(goodsListKey,JsonUtil.object2JsonStr(list));//==============错误代码==================return BaseResult.success(pageInfo);}else {//如果没有数据,将空数据放入缓存,设置失效时间60svalueOperations.set(goodsListKey,JsonUtil.object2JsonStr(new PageInfo<>(new ArrayList<Goods>())),60, TimeUnit.SECONDS);}return BaseResult.error();}
}

首先需要知道有哪些产品是动态的,哪些产品是会实时改变的。
已知商品分类id,品牌id,商品名称,分页页码,每页显示条数是固定的
因此我们可以使用一个数组来存储信息,当发生改变时,改变数组的内容。将本次请求中所有动态数据放入数据中,得到一个最终的goodsListKey,存入redis缓存中,如果以前查询过而且没有过期,那么从redis缓存中读取数据,如果没有查询过,则到数据库中查询。
如果查询出的数据为空,那么为了防止缓存雪崩的问题,那么我们采用返回一个空数据,并设置他的失效时间。
知识点:mybatis插件pageHelper
这里使用了分页对象PageInfo,pageInfo可以方便的封装分页信息,比如总记录,页码,每页显示条数等,并且他还封装了方法,可以让我们通过调用方法直接获取总记录条数等。

第一步,导入依赖
第二步,创建页对象 PageHelper.startPage(pageNum,pageSize);
第三步,创建分页对象 PageInfo<Good> pageInfo = new PageInfo(list);
第四步,返回分页对象到前端,渲染页面。

http://www.ppmy.cn/news/64022.html

相关文章

关于ADC的笔记1

ADC&#xff0c;全称Anlog-to-Digital Converter&#xff0c;模拟/数字转换器。是指将连续变量的模拟信号转换为离散的数字信号的器件&#xff0c;我们能通过ADC将外界的电压值读入我们的单片机中. 常见的ADC有两种 1.并联比较型&#xff1a; 它的优点是转换速度最快&#x…

操作系统相关问题——应用程序和操作系统怎么配合

应用程序和操作系统都是软件&#xff0c; CPU会将它们一视同仁&#xff0c;甚至CPU不知道自己在执行的程序是操作系统还是一般应用软件。CPU只知道去cs:ip寄存器中指向的内存取出指令并执行&#xff0c;它不知道什么是操作系统。 编程语言其实只是编译器和大家的约定&#xff…

Django框架之视图HttpResponse 对象

本篇文章主要内容为&#xff1a;视图中HttpResponse对象的属性、方法及json、redirect子类包含使用cookie使用、跳转、json返回的示例。 概述 HttpResponse对象是对用户访问的响应&#xff0c;与HttpRequest对象由django创建&#xff0c;HttpResponse对象是由开发人员创建。Ht…

微服务高频面试题

1、Spring Cloud 5大组件有哪些&#xff1f; 早期我们一般认为的Spring Cloud五大组件是 Eureka : 注册中心Ribbon : 负载均衡Feign : 远程调用Hystrix : 服务熔断Zuul/Gateway : 网关 随着SpringCloudAlibba在国内兴起 , 我们项目中使用了一些阿里巴巴的组件 注册中心/配置…

1960-2014年各国二氧化碳排放量(人均公吨数)

1960&#xff0d;2014年各国二氧化碳排放量&#xff08;人均公吨数&#xff09;&#xff08;世界发展指标, 2019年12月更新&#xff09; 1、来源&#xff1a;世界发展指标 2、时间&#xff1a;1960&#xff0d;2014年 3、范围&#xff1a;世界各国 4、指标&#xff1a; 二氧…

数据可视化工具 - ECharts以及柱状图的编写

1 快速上手 引入echarts 插件文件到html页面中 <head><meta charset"utf-8"/><title>ECharts</title><!-- step1 引入刚刚下载的 ECharts 文件 --><script src"./echarts.js"></script> </head>准备一个…

二叉树原理

二叉树原理TOC 二叉树是公认的难解型数据结构&#xff0c;这里试着系统化的讨论一些与二叉树有关的内容&#xff1a; 二叉树的数据标记方法&#xff1a;层次数据&#xff08;int t[N];&#xff09;,左右树标记&#xff08;bool lr:0standForL, 1standForR&#xff09;&#xf…

软考--快速掌握七层模型与各种协议的划分

目录 协议族 网络层涉及的协议 传输层涉及的协议 应用层涉及的协议 协议族 认识几个协议族&#xff0c;所谓协议族就是说他不是单一的协议。而是很多协议拼在一起的。 TCP/IP协议族是internet的标准协议族&#xff0c;所以使用广&#xff0c;但是tcp/ip协议族传输效率是比较…