SpringBoot+Redis+RabbitMQ完成增删改查

news2024/10/5 8:33:57

各部分分工职责

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 {

    @Bean
    public 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;
    }

    @Bean
    public 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 队列对象
     */
    @Bean
    public Queue queueSaveUpdate() {
        log.info(QUEUE_SAVE_UPDATE + ":RabbitMQ队列初始化成功:" + LocalDateTime.now());
        return new Queue(QUEUE_SAVE_UPDATE, true); // durable: 是否持久化
    }

    /**
     * 删除定义队列
     * @return 队列对象
     */
    @Bean
    public Queue queueDelete() {
        log.info(QUEUE_DELETE + ":RabbitMQ队列初始化成功:" + LocalDateTime.now());
        return new Queue(QUEUE_DELETE, true); // durable: 是否持久化
    }

    /**
     * 定义交换机
     * @return 交换机对象
     */
    @Bean
    public TopicExchange exchange() {
        log.info(EXCHANGE_NAME + ":RabbitMQ交换机初始化成功:" + LocalDateTime.now());
        return new TopicExchange(EXCHANGE_NAME);
    }

    /**
     * 绑定队列和交换机
     * @return 绑定对象
     */
    @Bean
    public Binding bindingSaveUpdate() {
        log.info(ROUTING_SAVE_UPDATE_KEY + ":RabbitMQ绑定队列和交换机成功:" + LocalDateTime.now());
        return BindingBuilder.bind(queueSaveUpdate()).to(exchange()).with(ROUTING_SAVE_UPDATE_KEY);
    }

    /**
     * 绑定队列和交换机
     * @return 绑定对象
     */
    @Bean
    public 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 {

    @Autowired
    private 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 {

    @Resource
    private BillMapper billMapper;

    @Resource
    private ObjectMapper objectMapper;

    @Resource
    private BillMessageSender billMessageSender;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    // redis 中 bill的键
    private static final String BILL_REDIS_KEY = "bill:info";


    /**
     * 保存或者修改信息
     * @param bill
     * @return
     */
    @Transactional
    public ApiResult saveUpdateByRedis(Bill bill){
        if(bill.getId() == null){
            billMessageSender.sendBillSaveUpdateMessage(bill);   //把要添加的信息放入消息队列
        }else {
            log.info("[ " + bill.getId() + " ] 修改缓存中的数据");
            String key = BILL_REDIS_KEY + bill.getId();          //找到要修改值对应的redis的key
            Map map = objectMapper.convertValue(bill, Map.class);//把对象转换成map
            redisTemplate.opsForHash().putAll(key, map);         //更新数据到缓存中
            billMessageSender.sendBillSaveUpdateMessage(bill);   //把要修改的信息放入消息队列
        }
        return ApiResult.success();
    }

    /**
     * 从缓存取数据
     * @param id
     * @return
     */
    @Transactional
    public 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的key
            Map map = objectMapper.convertValue(bill, Map.class);   //把对象转换成map
            redisTemplate.opsForHash().putAll(putKey, map);         //更新数据到缓存中
        }
        return bill;
    }

    /**
     * 删除方法
     * @param ids
     * @return
     */
    @Transactional
    public 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 {

    @Autowired
    private BillMapper billMapper;
    @Autowired
    private ObjectMapper objectMapper;
    @Resource
    private 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的key
        Map map = objectMapper.convertValue(bill, Map.class);//把对象转换成map
        redisTemplate.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.coloradmin.cn/o/2189584.html

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

相关文章

【DataLoom】智能问数 - 自然语言与数据库交互

探索DataLoom的智能问数功能&#xff1a;简化数据库查询 在数据驱动的决策制定中&#xff0c;数据库查询是获取洞察的关键步骤。但是&#xff0c;传统的数据库查询方法往往复杂且技术性强&#xff0c;这限制了非技术用户的使用。DataLoom的智能问数功能正是为了解决这一问题而…

【WebGis开发 - Cesium】如何确保Cesium场景加载完毕

目录 引言一、监听场景加载进度1. 基础代码2. 加工代码 二、进一步封装代码1. 已知存在的弊端2. 封装hooks函数 三、使用hooks方法1. 先看下效果2. 如何使用该hooks方法 三、总结 引言 本篇为Cesium开发的一些小技巧。 判断Cesium场景是否加载完毕这件事是非常有意义的。 加载…

Spring Boot中线程池使用

说明&#xff1a;在一些场景&#xff0c;如导入数据&#xff0c;批量插入数据库&#xff0c;使用常规方法&#xff0c;需要等待较长时间&#xff0c;而使用线程池可以提高效率。本文介绍如何在Spring Boot中使用线程池来批量插入数据。 搭建环境 首先&#xff0c;创建一个Spr…

自动驾驶系列—颠覆未来驾驶:深入解析自动驾驶线控转向系统技术

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

【树莓派系列】交叉编译工具、交叉编译链的安装使用

交叉编译工具、交叉编译链的安装使用 文章目录 交叉编译工具、交叉编译链的安装使用一、交叉编译1.1什么是交叉编译1.2为什么要交叉编译1.3宿主机和目标机 二、搭建交叉编译工作环境2.1安装工具链2.2配置环境变量● 配置临时环境变量● 配置永久环境变量 三、交叉编译宿主机和目…

NASA:Seasat-A 散射计(SASS)得出的风速和风向矢量数据集

目录 简介 摘要 代码 引用 网址推荐 0代码在线构建地图应用 机器学习 SEASAT SCATTEROMETER DEALIASED OCEAN WIND VECTORS (Atlas) 简介 SEASAT散射计反回波强度&#xff08;scattering&#xff09;提供了对海面风速和风向的估计。SEASAT散射计被用来获取海面风场的信…

LabVIEW提高开发效率技巧----调度器设计模式

在LabVIEW开发中&#xff0c;针对多任务并行的需求&#xff0c;使用调度器设计模式&#xff08;Scheduler Pattern&#xff09;可以有效地管理多个任务&#xff0c;确保它们根据优先级或时间间隔合理执行。这种模式在需要多任务并发执行时特别有用&#xff0c;尤其是在实时系统…

【算法】---归并排序(递归非递归实现)

参考 左程云算法 算法导论 前言 本篇介绍 归并排序分治法 前置知识 了解递归&#xff0c; 了解数组。 引入 归并排序 归并排序最早是由公认的现代计算机之父John von Neumann发明的&#xff0c; 这是一种典型的分治思想应用。 我们先介绍分治思想 分治思想 分治思想的…

java:pdfbox 3.0 去除扫描版PDF中文本水印

官网下载 https://pdfbox.apache.org/download.html下载 pdfbox-app-3.0.3.jar cd D:\pdfbox 运行 java -jar pdfbox-app-3.0.3.jar java -jar pdfbox-app-3.0.3.jar Usage: pdfbox [COMMAND] [OPTIONS] Commands:debug Analyzes and inspects the internal structu…

(C语言贪吃蛇)7.显示贪吃蛇完整身体改进

前言 上节显示了贪吃蛇身子的三个节点&#xff0c;但是吃了食物后蛇身变长应该如何操作&#xff0c;本节给出答案。 一、贪吃蛇身体是什么&#xff1f; 使用链表这个数据结构来动态的显示贪吃蛇的身体。 二、对贪吃蛇身体进行改进 1.贪吃蛇身子显示 代码如下&#xff1a; …

信息学奥赛使用的编程IDE:Dev-C++ 安装指南

信息学奥赛&#xff08;NOI&#xff09;作为全国性的编程竞赛&#xff0c;要求参赛学生具备扎实的编程能力&#xff0c;而熟练使用适合的编程工具则是学习与竞赛的基础。在众多编程环境中&#xff0c;Dev-C IDE 因其简洁、轻量、支持C编程等特点&#xff0c;成为许多参赛者的常…

最新版的dubbo服务调用(用nacos做注册中心用)

一、介绍 1.1、什么是 nacos Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service的首字母简称&#xff0c;一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。 Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集&a…

Java 每日一刊(第21期):反射机制

文章目录 前言动态插件系统面临的问题如何在运行时动态加载和调用类与方法设计模式的尝试引入反射 Java 反射的核心概念Class 类Constructor 类Method 类Field 类 Java 反射的应用场景框架开发插件系统序列化与反序列化动态代理测试工具 反射的优缺点反射实战动态加载类并调用方…

【hot100-java】【将有序数组转换为二叉搜索树】

二叉树篇 BST树 递归直接实现。 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode left, TreeNo…

【C++差分数组】2381. 字母移位 II|1793

本文涉及知识点 C差分数组 LeetCode2381. 字母移位 II 给你一个小写英文字母组成的字符串 s 和一个二维整数数组 shifts &#xff0c;其中 shifts[i] [starti, endi, directioni] 。对于每个 i &#xff0c;将 s 中从下标 starti 到下标 endi &#xff08;两者都包含&#…

STM32的串行外设接口SPI

一、SPI简介 1.SPI总线特点 &#xff08;1&#xff09;四条通信线 SPI需要SCK、MISO、MOSI、NSS四条通信线来完成数据传输 &#xff0c;每增加一个从机&#xff0c;多一条NSS通信线。 &#xff08;2&#xff09;多主多从 SPI总线允许有多个主机和多个从机。 &#xff08;3&…

再见 ESNI,你好 ECH!—— ECH的前世今生

译者注&#xff1a;2024 年 9 月 25 日&#xff0c;Cloudflare 宣布再次推出 ECH 功能。借此契机&#xff0c;本人翻译了 Cloudflare 介绍 ECH 的博文 Good-bye ESNI, hello ECH! &#xff0c;以便科普ECH的发展历程。 现代互联网上的大多数通信都经过加密&#xff0c;以确保其…

Flink源码剖析

写在前面 最近一段时间都没有更新博客了&#xff0c;原因有点离谱&#xff0c;在实现flink的两阶段提交的时候&#xff0c;每次执行自定义的notifyCheckpointComplete时候&#xff0c;好像就会停止消费数据&#xff0c;完成notifyComplete后再消费数据&#xff1b;基于上述原因…

在Stable Diffusion WebUI中安装SadTalker插件时几种错误提示的处理方法

SD中的插件一般安装比较简单&#xff0c;但也有一些插件安装会比较难。比如我在安装SadTalker时&#xff0c;就遇到很多问题&#xff0c;一度放弃了&#xff0c;后来查了一些网上攻略&#xff0c;自己也反复查看日志&#xff0c;终于解决&#xff0c;不吐不快。 一、在Stable …

ElasticSearch高级功能详解与读写性能调优

目录 1. ES数据预处理 1.1 Ingest Node Ingest Node VS Logstash 1.2 Ingest Pipeline Pipeline & Processor 创建pipeline 使用pipeline更新数据 借助update_by_query更新已存在的文档 1.3 Painless Script Painless的用途&#xff1a; 通过Painless脚本访问字…