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 {

    @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.mfbz.cn/a/888623.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

leetcode C++特性 AIDL的一些细节

leetcode细节 C的一些特性 【C基础】std::move用法介绍-CSDN博客 c thread的join和joinable的区别_thread joinable-CSDN博客 C线程介绍_std::thread 头文件-CSDN博客 https://blog.csdn.net/weixin_46645965/article/details/136259902 【C】—— 观察者模式-CSDN博客 C 迭…

(笔记)第三期书生·浦语大模型实战营(十一卷王场)–书生基础岛第3关---浦语提示词工程实践

学员闯关手册&#xff1a;https://aicarrier.feishu.cn/wiki/ZcgkwqteZi9s4ZkYr0Gcayg1n1g?open_in_browsertrue 课程视频&#xff1a;https://www.bilibili.com/video/BV1cU411S7iV/ 课程文档&#xff1a; https://github.com/InternLM/Tutorial/tree/camp3/docs/L1/Prompt 关…

Linux-分析 IO 瓶颈手册

分析IO瓶颈 此文主要内容&#xff1a;I/O性能重要指标、主要排查工具、主要排查手段、工具图示 磁盘 I/O 性能指标 四个核心的磁盘 I/O 指标 使用率&#xff1a;是指磁盘忙处理 I/O 请求的百分比。过高的使用率&#xff08;比如超过 60%&#xff09;通常意味着磁盘 I/O 存在…

办公AI推荐:阅读总结视频翻译文档文章等—包阅AI

目录 官网首页 网页阅读 思维导图 图书对话功能 1. 关键词 2. 总结 3. 主要内容 随心笔记 视频阅读 Mysql数据库案例 思维导图 内容评价 总结 想象一下&#xff0c;当您能在几分钟内掌握一小时视频的精华&#xff0c;或瞬间生成一本书的思维导图&#xff0c;您的学…

22.第二阶段x86游戏实战2-背包遍历REP指令详解

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 本人写的内容纯属胡编乱造&#xff0c;全都是合成造假&#xff0c;仅仅只是为了娱乐&#xff0c;请不要…

【AIGC】Exa AI 要做 AI 领域的 Google

又一个AI搜索引擎诞生&#xff1a;Exa AI。 与其他旨在取代谷歌的AI驱动搜索引擎不同&#xff0c;Exa的目标是创建一个专门为AI设计的搜索工具。 Exa的使命: 互联网包含人类的集体知识&#xff0c;但目前的搜索体验更像在垃圾场中导航&#xff0c;而非在知识图书馆中漫游。核…

SQL第14课挑战题

1. 将两个select语句结合起来&#xff0c;以便从OrderItems表中检索产品ID(prod_id)和quantity。其中&#xff0c;一个select语句过滤数量为100的行&#xff0c;另一个select语句过滤ID以BNBG开头的产品。按产品ID对结果进行排序。 2. 重新第一题&#xff0c;仅使用单个select语…

怎样查局域网里的所有ip?

如果想高效管理网络设备&#xff0c;识别配置、更新和维护各类连接设备&#xff0c;排查网络故障&#xff0c;提升网络安全性&#xff0c;监控异常 IP 活动&#xff0c;发现潜在威胁等需要知道局域网。那么怎样查局域网里的所有ip呢&#xff1f; 一、局域网IP是什么&#xff1…

【AI学习】Mamba学习(四):从SSM开始

Mamba的发展&#xff0c;是从SSM->HiPPO->S4->Mamba 演化过来。所以&#xff0c;了解Mamba&#xff0c;得从SSM开始。 SSM&#xff0c;状态空间模型 SSM&#xff0c;就是状态空间模型。 为什么需要SSM&#xff1f;查看三十年前的教科书&#xff0c;控制论的发展&…

重学SpringBoot3-集成Redis(五)之布隆过滤器

更多SpringBoot3内容请关注我的专栏&#xff1a;《SpringBoot3》 期待您的点赞&#x1f44d;收藏⭐评论✍ 重学SpringBoot3-集成Redis&#xff08;五&#xff09;之布隆过滤器 1. 什么是布隆过滤器&#xff1f;基本概念适用场景 2. 使用 Redis 实现布隆过滤器项目依赖Redis 配置…

netdata保姆级面板介绍

netdata保姆级面板介绍 基本介绍部署流程下载安装指令选择设置KSM为什么要启用 KSM&#xff1f;如何启用 KSM&#xff1f;验证 KSM 是否启用注意事项 检查端口启动状态 netdata和grafana的区别NetdataGrafananetdata各指标介绍总览system overview栏仪表盘1. CPU2. Load3. Disk…

NUKE 15有哪些新的改进功能?影视后期特效合成NUKE 15 安装包分享 【Mac/win】

Nuke 15是一款由英国The Foundry公司开发的专业的合成软件&#xff0c;被广泛用于电影、电视和广告制作中的后期合成和特效制作。 Nuke 15拥有强大的功能和灵活性&#xff0c;可以帮助用户处理各种复杂的合成任务&#xff0c;包括图像修复、色彩校正以及粒子特效等。它具备高效…

Spring Validation —— 参数校验框架

案例说明——后端校验注册表单字段 在编写注册功能时&#xff0c;需要考虑字段校验的情况&#xff0c;这时候可以采用 Spring提供的一套参数校验框架工具——Spring Validation。一下是使用的步骤&#xff1a; 1. 导入validation坐标 2. 在参数上添加 Pattern注解&#xff0c…

尚硅谷javaSpring

尚硅谷课件: 分类&#xff1a;尚硅谷Spring6教程 - Lixx Blog - 李晓旭的博客 简介 Java Spring 是一个开源的、全面的企业级应用开发框架&#xff0c;旨在简化企业级应用的开发。Spring 框架最初由 Rod Johnson 在 2002 年发布&#xff0c;并随着时间的推移&#xff0c;它已…

【源码+文档】基于Java的新能源停车场管理系统的设计与实现

&#x1f6a9;如何选题&#xff1f; 如何选题、让题目的难度在可控范围&#xff0c;以及如何在选题过程以及整个毕设过程中如何与老师沟通&#xff0c;这些问题是需要大家在选题前需要考虑的&#xff0c;具体的方法我会在文末详细为你解答。 &#x1f6ad;如何快速熟悉一个项…

低质量数据的多模态融合方法

目录 多模态融合 低质量多模态融合的核心挑战 噪声多模态数据学习 缺失模态插补 平衡多模态融合 动态多模态融合 启发式动态融合 基于注意力的动态融合 不确定性感知动态融合 论文 多模态融合 多模态融合侧重于整合多种模态的信息,以实现更准确的预测,在自动驾驶、…

【小沐学GIS】blender导入OpenTopography地形数据(BlenderGIS、OSM、Python)

文章目录 1、简介1.1 blender1.2 OpenStreetMap地图 2、BlenderGIS2.1 下载BlenderGIS2.2 安装BlenderGIS2.3 申请opentopography的key2.4 抓取卫星地图2.5 生成高度图2.6 获取OSM数据 结语 1、简介 1.1 blender https://www.blender.org/ Blender 是一款免费的开源 3D 创作套…

【c++】初步了解类和对象2

1、类的作用域 类定义了一个新的作用域&#xff0c;类的所有成员都在类的作用域中。在类体外定义成员时&#xff0c;需要使用 :: 作用域操作符指明成员属于哪个类域。 如图&#xff0c;此时在类内声明了函数firstUniqChar()&#xff0c;在类外进行了函数体的具体定义。 但是却…

使用 classification_report 评估 scikit-learn 中的分类模型

介绍 在机器学习领域&#xff0c;评估分类模型的性能至关重要。scikit-learn 是一个功能强大的 Python 机器学习工具&#xff0c;提供了多种模型评估工具。其中最有用的函数之一是 classification_report&#xff0c;它可以全面概述分类模型的关键指标。在这篇文章中&#xff…

国庆作业

day1 1.开发环境 Linux系统GCCFDBmakefilesqlite3 2.功能描述 项目功能: 服务器&#xff1a;处理客户端的请求&#xff0c;并将数据存入数据库中&#xff0c;客户端请求的数据从数据库进行获取&#xff0c;服务器转发给客户端。 用户客户端&#xff1a;实现账号的注册、登…