SpringBoot+Redis+RabbitMQ完成增删改查

各部分分工职责

RabbitMQ负责添加、修改、删除的异步操作
Redis负责数据的缓存

RabbitMQ里面角色职责简单描述

RabbitMQ里面有几个角色要先分清以及他们的对应关系:

交换机、队列、路由键
交换机和队列是一对多
队列和路由键是多对多

然后就是消息的发送者(生产者)和消息的接受者(消费者)

此案例中,添加修改删除要从生产者发送到消费者,也就是说,消费者才是具体干活的角色,消息生产者只需要把消息发送到对应的队列中,由交换机根据路由键发送到对应的队列中

Redis职责简单描述

Redis只需要把要看的数据以及新添加的数据,添加到缓冲中即可,如果缓冲中没有,就从数据库查,再添加到缓存中,所以此次数据类型用的Hash

pom.xml文件坐标引入

        <!-- redis工具 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- JSON工具 https://mvnrepository.com/artifact/com.alibaba/fastjson --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.47</version></dependency><!--spring2.X集成redis所需common-pool2--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId><version>2.6.0</version></dependency><!-- Spring Boot Starter AMQP --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency>

SpringBoot的配置文件

# RabbitMQ:配置,服务器地址,端口,用户名,密码
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest# 使用 Redis 作为缓存存储,具体配置:服务器地址,端口,密码
spring.cache.type=redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
#spring.redis.password=root
spring.redis.password=
# 连接工厂使用的数据库索引,redis默认有16个db,索引0-15
spring.redis.database=0
#spring.redis.timeout=0
# 连接池最大连接数(使用负值表示没有限制) 这个值决定了同时可以有多少个活动的连接
spring.redis.lettuce.pool.max-active=8
## 连接池最大阻塞等待时间(-1表示没有限制) 当连接池中的所有连接都被占用时,新的请求会等待一段时间
spring.redis.lettuce.pool.max-wait=-1
## 连接池中的最大空闲连接,连接池中最多可以有多少个空闲的连接
spring.redis.lettuce.pool.max-idle=8
## 连接池中的最小空闲连接,连接池中至少要有多少个空闲的连接
spring.redis.lettuce.pool.min-idle=0

Redis配置类

@EnableCaching      //开启缓存
@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(factory);// 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 Redis 的值(Value)GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();// 使用 StringRedisSerializer 来序列化和反序列化 Redis 的键(Key)StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();// 设置键(key)的序列化器template.setKeySerializer(stringRedisSerializer);// 设置值(value)的序列化器template.setValueSerializer(jackson2JsonRedisSerializer);// 设置 Hash 键(key)的序列化器template.setHashKeySerializer(stringRedisSerializer);// 设置 Hash 值(value)的序列化器template.setHashValueSerializer(jackson2JsonRedisSerializer);template.afterPropertiesSet();return template;}@Beanpublic CacheManager cacheManager(RedisConnectionFactory factory) {RedisSerializer<String> redisSerializer = new StringRedisSerializer();Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);//解决查询缓存转换异常的问题ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(om);// 配置序列化(解决乱码的问题),过期时间600秒RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(600))  //设置数据过期时间600秒.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)).disableCachingNullValues();RedisCacheManager cacheManager = RedisCacheManager.builder(factory).cacheDefaults(config).build();return cacheManager;}
}

RabbitMQ配置类

如果没有配置logback,就把log.info()的代码全部删了,不影响运行,后面有的话也都删了

@Configuration
public class RabbitMQConfig {public static final String EXCHANGE_NAME = "bill.exchange"; // 交换机名称public static final String QUEUE_SAVE_UPDATE = "bill.saveupdate"; // 保存修改队列public static final String QUEUE_DELETE = "bill.delete"; // 删除队列public static final String ROUTING_SAVE_UPDATE_KEY = "bill.saveupdatekey"; // 保存修改路由键public static final String ROUTING_DELETE_KEY = "bill.deletekey"; // 删除路由键/*** 添加/修改定义队列* @return 队列对象*/@Beanpublic Queue queueSaveUpdate() {log.info(QUEUE_SAVE_UPDATE + ":RabbitMQ队列初始化成功:" + LocalDateTime.now());return new Queue(QUEUE_SAVE_UPDATE, true); // durable: 是否持久化}/*** 删除定义队列* @return 队列对象*/@Beanpublic Queue queueDelete() {log.info(QUEUE_DELETE + ":RabbitMQ队列初始化成功:" + LocalDateTime.now());return new Queue(QUEUE_DELETE, true); // durable: 是否持久化}/*** 定义交换机* @return 交换机对象*/@Beanpublic TopicExchange exchange() {log.info(EXCHANGE_NAME + ":RabbitMQ交换机初始化成功:" + LocalDateTime.now());return new TopicExchange(EXCHANGE_NAME);}/*** 绑定队列和交换机* @return 绑定对象*/@Beanpublic Binding bindingSaveUpdate() {log.info(ROUTING_SAVE_UPDATE_KEY + ":RabbitMQ绑定队列和交换机成功:" + LocalDateTime.now());return BindingBuilder.bind(queueSaveUpdate()).to(exchange()).with(ROUTING_SAVE_UPDATE_KEY);}/*** 绑定队列和交换机* @return 绑定对象*/@Beanpublic Binding bindingDelete() {log.info(ROUTING_DELETE_KEY + ":RabbitMQ绑定队列和交换机成功:" + LocalDateTime.now());return BindingBuilder.bind(queueDelete()).to(exchange()).with(ROUTING_DELETE_KEY);}
}

RabbitMQ配置消息发送者(生产者)

也就是说,在需要异步调用的地方,注入BillMessageSender,然后,调对应的方法就可以了

@Service
public class BillMessageSender {@Autowiredprivate RabbitTemplate rabbitTemplate;/*** 发送用户添加修改消息* @param bill 参数对象*/public void sendBillSaveUpdateMessage(Bill bill) {rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, RabbitMQConfig.ROUTING_SAVE_UPDATE_KEY, bill);}/*** 发送用户删除消息* @param ids 参数列表*/public void sendBillDeleteMessage(List<Long> ids) {rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, RabbitMQConfig.ROUTING_DELETE_KEY, ids);}
}

异步发送消息

这是在Service层,
所以把ApiResult()这个自定义返回类换成你们的就行了
ObjectMapper这个工具主要是用来处理JSON数据的,这里我用是因为为了方便实体类和Map之间相互转换的,BillMapper是我自己用到的,这个可以换成你们自己的,不影响,剩下的就基本上没啥了,有不懂的可以评论区问,看到会回复

@Slf4j
@Service
public class BillRedisService {@Resourceprivate BillMapper billMapper;@Resourceprivate ObjectMapper objectMapper;@Resourceprivate BillMessageSender billMessageSender;@Resourceprivate RedisTemplate<String, Object> redisTemplate;// redis 中 bill的键private static final String BILL_REDIS_KEY = "bill:info";/*** 保存或者修改信息* @param bill* @return*/@Transactionalpublic ApiResult saveUpdateByRedis(Bill bill){if(bill.getId() == null){billMessageSender.sendBillSaveUpdateMessage(bill);   //把要添加的信息放入消息队列}else {log.info("[ " + bill.getId() + " ] 修改缓存中的数据");String key = BILL_REDIS_KEY + bill.getId();          //找到要修改值对应的redis的keyMap map = objectMapper.convertValue(bill, Map.class);//把对象转换成mapredisTemplate.opsForHash().putAll(key, map);         //更新数据到缓存中billMessageSender.sendBillSaveUpdateMessage(bill);   //把要修改的信息放入消息队列}return ApiResult.success();}/*** 从缓存取数据* @param id* @return*/@Transactionalpublic Bill selectPrimaryKeyByRedis(Long id){Bill bill;String key = BILL_REDIS_KEY + id;//有这个键就取数据,不然就查数据库if (redisTemplate.hasKey(key)) {log.info("[ " + id + " ] 从缓存中取数据");Map<Object, Object> map = redisTemplate.opsForHash().entries(key);bill = objectMapper.convertValue(map, Bill.class);}else{log.info("[ " + id + " ] 缓存中没有,向数据库中查询数据");bill = billMapper.selectByPrimaryKey(id);String putKey = BILL_REDIS_KEY + bill.getId();          //找到要修改值对应的redis的keyMap map = objectMapper.convertValue(bill, Map.class);   //把对象转换成mapredisTemplate.opsForHash().putAll(putKey, map);         //更新数据到缓存中}return bill;}/*** 删除方法* @param ids* @return*/@Transactionalpublic ApiResult delByRedis(List<Long> ids) {log.info("[ " + Arrays.toString(ids.toArray()) + " ] 以上数据要被删除");for (Long id : ids) {String key = BILL_REDIS_KEY + id;redisTemplate.delete(key);  //删除缓存中的数据}//数据库信息交给消息队列删除billMessageSender.sendBillDeleteMessage(ids);return ApiResult.success();}
}

RabbitMQ配置消息接收者(消费者)

添加上这个注解@RabbitListener(queues = RabbitMQConfig.QUEUE_SAVE_UPDATE),
并指明监听的队列queues = RabbitMQConfig.QUEUE_SAVE_UPDATE
就能获取到消息发送者发送过来的任务以及任务参数了,就可以在这里写处理逻辑了,如果没有配置logback,可以把@Slf4j,以及log.info(),这两个代码删除了

@Slf4j
@Component
public class BillMessageReceiver {@Autowiredprivate BillMapper billMapper;@Autowiredprivate ObjectMapper objectMapper;@Resourceprivate RedisTemplate<String, Object> redisTemplate;// redis 中 bill的键private static final String BILL_REDIS_KEY = "bill:info";/*** 处理添加和修改操作* @param bill 参数对象*/@Transactional@RabbitListener(queues = RabbitMQConfig.QUEUE_SAVE_UPDATE)public void receiveBillSaveUpdateMessage(Bill bill) {log.info(RabbitMQConfig.QUEUE_SAVE_UPDATE + " 队列获取到数据:" + bill.toString());if (bill == null) {return;}if (bill.getId() == null) {bill.setDeleted(0);bill.setCreateTime(new Date());billMapper.insertSelective(bill);} else {bill.setUpdateTime(new Date());billMapper.updateByPrimaryKeySelective(bill);}String key = BILL_REDIS_KEY + bill.getId();          //添加后就有主键了,拼接成redis的keyMap map = objectMapper.convertValue(bill, Map.class);//把对象转换成mapredisTemplate.opsForHash().putAll(key, map);         //把添加的数据放到缓存中}/*** 删除数据* @param ids 参数列表*/@Transactional@RabbitListener(queues = RabbitMQConfig.QUEUE_DELETE)public void receiveBillDeleteMessage(List<Long> ids) {log.info(RabbitMQConfig.QUEUE_DELETE + " 队列获取到数据:" + Arrays.toString(ids.toArray()));Bill bill = new Bill();bill.setDeleted(1);Example example = new Example(Bill.class);Example.Criteria criteria = example.createCriteria();criteria.andIn("id",ids);billMapper.updateByExampleSelective(bill,example);}}

总结

再把逻辑捋一下

添加修改删除,这些操作统一发送给RabbitMQ,由RabbitMQ的消费者处理后续操作

查看详情,添加和更新的数据,交给Redis缓存,缓存没有,就查数据库,然后再缓存到Redis中,就第一遍查数据库,后续走的都是缓存

以上代码实现的功能就是,

全部数据查询还是走的数据库(数据量不多),但是单个查询,查询详情,先查缓存,缓存没有再查数据库,然后再添加到缓存中,下次查询就不走数据库了

添加修改删除统一发送给RabbitMQ消息队列,由消息队列异步完成后续的任务,并更新或者删除对应的缓存

这比之前单独的对数据库操作,多了2层逻辑,RabbitMQ和缓存的处理,这个例子就是简单的使用RabbitMQ和Redis,算是个小入门,如果有其他好的建议,可以评论一下,十分感谢!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/55408.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

课设实验-数据结构-线性表-手机销售

题目&#xff1a; 代码&#xff1a; #include<stdio.h> #include<string.h> #define MaxSize 10 //定义顺序表最大长度 //定义手机结构体类型 typedef struct {char PMod[10];//手机型号int PPri;//价格int PNum;//库存量 }PhoType; //手机类型 //记录手机的顺序…

【HTTP(3)】(状态码,https)

【认识状态码】 状态码最重要的目的&#xff0c;就是反馈给浏览器:这次请求是否成功&#xff0c;若失败&#xff0c;则出现失败原因 常见状态码: 200:OK&#xff0c;表示成功 404:Not Found&#xff0c;浏览器访问的资源在服务器上没有找到 403:Forbidden&#xff0c;访问被…

springboot系列--web相关知识探索三

一、前言 web相关知识探索二中研究了请求是如何映射到具体接口&#xff08;方法&#xff09;中的&#xff0c;本次文章主要研究请求中所带的参数是如何映射到接口参数中的&#xff0c;也即请求参数如何与接口参数绑定。主要有四种、分别是注解方式、Servlet API方式、复杂参数、…

【案例】距离限制模型透明

开发平台&#xff1a;Unity 2023 开发工具&#xff1a;Unity ShaderGraph   一、效果展示 二、路线图 三、案例分析 核心思路&#xff1a;计算算式&#xff1a;透明值 实际距离 / 最大距离 &#xff08;实际距离 ≤ 最大距离&#xff09;   3.1 说明 | 改变 Alpha 值 在 …

计算机组成原理之无符号整数的表示和运算

无符号整数的表示 无符号整数的表示&#xff1a;无符号整数直接使用其二进制形式表示&#xff0c;所有位都是数值位&#xff0c;没有符号位。例如&#xff0c;一个8位的无符号整数可以表示的范围是从0&#xff08;00000000&#xff09;到255&#xff08;11111111&#xff09;。…

stm32f103调试,程序与定时器同步设置

在调试定时器相关代码时&#xff0c;注意到定时器的中断位总是置1&#xff0c;怀疑代码有问题&#xff0c;经过增大定时器的中断时间&#xff0c;发现定时器与代码调试并不同步&#xff0c;这一点对于调试涉及定时器的代码是非常不利的&#xff0c;这里给出keil调试stm32使定时…

自用Proteus(8.15)常用元器件图示和功能介绍(持续更新...)

文章目录 一、 前言二、新建工程&#xff08;以51单片机流水灯为例&#xff09;2.1 打开软件2.2 建立新工程2.3 创建原理图2.4 不创建PCB布版设计2.5 创建成功2.6 添加元器件2.7 原理图放置完成2.8 编写程序&#xff0c;进行仿真2.9 仿真 三、常用元器件图示和功能介绍3.1 元件…

【回眸】Tessy 单元测试软件使用指南(四)常见报错及解决方案与批量初始化的经验

前言 分析时Tessy的报错 1.fatal error: Tricore/Compilers/Compilers.h: No such file or directory 2.error: #error "Compiler unsupported" 3.warning: invalid suffix on literal;C11 requires a space between literal and string macro 4.error: unknown…

螺蛳壳里做道场:老破机搭建的私人数据中心---Centos下Docker学习01(环境准备)

1 准备工作 由于创建数据中心需要安装很多服务器&#xff0c;这些服务器要耗费很所物理物理计算资源、存储资源、网络资源和软件资源&#xff0c;作为穷学生只有几百块的n手笔记本&#xff0c;不可能买十几台服务器来搭建数据中心&#xff0c;也不愿意跑实验室&#xff0c;想躺…

第十二章--- fixed 和 setprecision 函数、round 函数、进制转换及底层逻辑

1. 保留几位小数 在C中&#xff0c;如果你想要控制输出的小数点后的位数&#xff0c;可以使用<iomanip>头文件提供的fixed和setprecision函数。这里的fixed用于设置浮点数的输出格式为定点表示法&#xff0c;而setprecision(n)则用来指定小数点后保留的位数。具体用法如…

文件上传之%00截断(00截断)以及pikachu靶场

pikachu的文件上传和upload-lab的文件上传 目录 mime type类型 getimagesize 第12关%00截断&#xff0c; 第13关0x00截断 差不多了&#xff0c;今天先学文件上传白名单&#xff0c;在网上看了资料&#xff0c;差不多看懂了&#xff0c;但是还有几个地方需要实验一下&#…

SpringBoot整合异步任务执行

同步任务&#xff1a; 同步任务是在单线程中按顺序执行&#xff0c;每次只有一个任务在执行&#xff0c;不会引发线程安全和数据一致性等 并发问题 同步任务需要等待任务执行完成后才能执行下一个任务&#xff0c;无法同时处理多个任务&#xff0c;响应慢&#xff0c;影响…

C++ 语言特性21 - 别名模板

一&#xff1a;概述 别名模板是 C11 引入的&#xff0c;用于为一个模板类型定义别名&#xff0c;从而简化复杂的模板类型定义。它结合了 using 关键字&#xff0c;可以对模板类型进行重新命名&#xff0c;使代码更加简洁和可读。 1. 作用 定义模板类型的别名。简化复杂的模板类…

VirtualBox+Vagrant快速搭建Centos7系统【最新详细教程】

VirtualBoxVagrant快速搭建Centos7系统 &#x1f4d6;1.安装VirtualBox✅下载VirtualBox✅安装 &#x1f4d6;2.安装Vagrant✅下载Vagrant✅安装 &#x1f4d6;3.搭建Centos7系✅初始化Vagrantfile文件生成✅启动Vagrantfile文件✅解决 vagrant up下载太慢的问题✅配置网络ip地…

Python--加载Hugging Face模型文件异常处理

尝试加载 Hugging Face 模型的配置文件时&#xff0c;无法从 https://huggingface.co 获取所需的 config.json 文件。这个错误通常是由于网络连接问题、访问受限或路径配置错误导致的。让我们一步步分析并解决这个问题。 可能原因及解决方案&#xff1a; 网络连接问题&#xf…

咸鱼sign逆向分析与爬虫实现

目标&#xff1a;&#x1f41f;的搜索商品接口 这个站异步有点多&#xff0c;好在代码没什么混淆。加密的sign值我们可以通过搜索找到位置 sign值通过k赋值&#xff0c;k则是字符串拼接后传入i函数加密 除了开头的aff…&#xff0c;后面的都是明文没什么好说的&#xff0c;我…

SysML案例-电磁轨道炮

DDD领域驱动设计批评文集>> 《软件方法》强化自测题集>> 《软件方法》各章合集>> 图片示例摘自intercax.com&#xff0c;作者是Intercax公司总裁Dirk Zwemer博士。

代码随想录:107、寻找存在的路径

107. 寻找存在的路径 这是道简单的并查集题目&#xff0c;设计插入&#xff0c;查找函数&#xff0c;比较基础 1、条件准备 father数组存每个结点的祖宗结点是谁 #include <bits/stdc.h>#define rep(i, l, r) for (int i l; i < r; i)using namespace std;#define…

Django学习笔记九:Django中间件Middleware

Django中间件&#xff08;Middleware&#xff09;是一段在Django的请求/响应处理过程中&#xff0c;可以介入并改变请求或响应的代码。中间件是Django框架中一个非常强大的功能&#xff0c;它允许你在Django的视图函数之前或之后执行自定义代码。 中间件可以用于&#xff1a; …

C题(六) 1到 100 的所有整数中出现多少个数字9

场景&#xff1a;编写程序数一下 1到 100 的所有整数中出现多少个数字9 控制循环的变量不可以随意改动&#xff01;&#xff01;&#xff01; 控制循环的变量不可以随意改动&#xff01;&#xff01;&#xff01; 控制循环的变量不可以随意改动&#xff01;&#xff01;&#x…