根据源码,模拟实现 RabbitMQ - 通过 SQLite + MyBatis 设计数据库(2)
目录
一、数据库设计
1.1、数据库选择
1.2、环境配置
1.3、建库建表接口实现
1.4、封装数据库操作
1.5、针对 DataBaseManager 进行单元测试
一、数据库设计
1.1、数据库选择
MySQL 是我们最熟悉的数据库,但是这里我们选择使用 SQLite,原因如下:
- SQLite 比 MySQL 更轻量:一个完整的 SQLite 数据库,只有一个单独的可执行文件(不到 1M).
- SQLite 操作简便:SQLite 只是一个本地数据库,相当于是直接操作本地的硬盘.
- SQLite 应用也非常广泛:在一些性能不高的设备上,SQLite 是数据库的首选,尤其是移动端和嵌入式设备(Android 系统就是内置的 SQLite).
1.2、环境配置
在 java 中直接使用 maven 把 SQLite 依赖引入即可(版本自行考虑)~
<!-- https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc --><dependency><groupId>org.xerial</groupId><artifactId>sqlite-jdbc</artifactId><version>3.41.2.1</version></dependency>
配置如下
spring:datasource:url: jdbc:sqlite:./data/meta.dbusername:password:driver-class-name: org.sqlite.JDBC
url:SQLite 的工作路径,用来存储数据在某个指定的文件中.
username & password:对于 SQLite 来说,不需要使用 用户名密码. MySQL 是一个客户端服务器结构的程序,而 SQLite 则不是客户端服务器结构的程序,只有本地主机能访问.
Ps:SQLite 虽然和 MySQL 不太一样,但是都可以通过 MyBatis 这样的框架来使用.
1.3、建库建表接口实现
存储的数据就是:交换机、队列、绑定.
这里我们使用 MyBatis 来完成相关的 CRUD.
mapper 接口中提供三个建库建表操作和针对这三个库表进行 CRUD 的操作.
@Mapper
public interface MetaMapper {//三个核心建表方法void createExchangeTable();void createQueueTable();void createBindingTable();//基于上述三个表,进行 插入、删除、查询 操作void insertExchange(Exchange exchange);List<Exchange> selectAllExchange();void deleteExchange(String exchangeName);void insertQueue(MSGQueue queue);List<MSGQueue> selectAllQueue();void deleteQueue(String queueName);void insertBinding(Binding binding);List<Binding> selectAllBinding();void deleteBinding(Binding binding);}
对应的实现如下:
<update id="createExchangeTable">create table if not exists exchange (name varchar(50) primary key,type int,durable boolean,autoDelete boolean,arguments varchar(1024));</update><update id="createQueueTable">create table if not exists queue (name varchar(50) primary key,durable boolean,exclusive boolean,autoDelete boolean,arguments varchar(1024));</update><update id="createBindingTable">create table if not exists binding (exchangeName varchar(50),queueName varchar(50),bindingKey varchar(256))</update><insert id="insertExchange" parameterType="com.example.rabbitmqproject.mqserver.core.Exchange">insert into exchange values (#{name}, #{type}, #{durable}, #{autoDelete}, #{arguments});</insert><select id="selectAllExchange" resultType="com.example.rabbitmqproject.mqserver.core.Exchange">select * from exchange;</select><delete id="deleteExchange" parameterType="com.example.rabbitmqproject.mqserver.core.Exchange">delete from exchange where name = #{name};</delete><insert id="insertQueue" parameterType="com.example.rabbitmqproject.mqserver.core.MSGQueue">insert into queue values(#{name}, #{durable}, #{exclusive}, #{autoDelete}, #{arguments});</insert><select id="selectAllQueue" resultType="com.example.rabbitmqproject.mqserver.core.MSGQueue">select * from queue;</select><delete id="deleteQueue">delete from queue where name = #{name};</delete><insert id="insertBinding">insert into binding values (#{exchangeName}, #{queueName}, #{bindingKey});</insert><select id="selectAllBinding" resultType="com.example.rabbitmqproject.mqserver.core.Binding">select * from binding;</select><delete id="deleteBinding">delete from binding where exchangeName = #{exchangeName} and queueName = #{queueName};</delete>
1.4、封装数据库操作
这里我们通过定制化 代码 的方式来自动完成建库建表的操作(符合 RabbitMQ 中间件的设定).

创建 DataBaseManager 类,来完成数据库相关的操作,注意细节如下:
- 初始化方法:一般谈到初始化,都会用到 构造方法,但是这里我们使用一个 普通的方法 —— init();构造方法一般是用来初始化类的属性,不会涉及到太多的业务逻辑,而此处的初始化,带有业务逻辑,还是单独领出来,手动来调用比较合适.
- 建库建表逻辑:这里期望,broker server 启动的时候做出如下逻辑判断:
- 如果数据库已经存在(表存在),不做任何操作.
- 如果数据库不存在,则建库建表,构造默认数据.
Ps:怎么判定数据库存在或者不存在?就判定 meta.db 文件是否存在即可(配置文件中的 url).
public class DataBaseManager {//这里不使用 Autowired 注解获取,因为当前这个类需要我们后面手动管理private MetaMapper metaMapper;//针对数据库进行初始化public void init() {//手动获取到 MetaMappermetaMapper = RabbitmqProjectApplication.context.getBean(MetaMapper.class);if(!checkDBExists()) {//数据库不存在,就进行建库建表操作//先创建出目录结构(否则会报错:找不到目录结构)File dataDir = new File("./data");dataDir.mkdirs();//创建数据库createTable();//插入默认数据createDefaultData();System.out.println("[DataBaseManager] 数据库初始化完成!");} else {//数据库存在,什么都不做即可System.out.println("[DataBaseManager] 数据库已存在!");}}private boolean checkDBExists() {File file = new File("./data/meta.db");return file.exists();}private void createTable() {metaMapper.createExchangeTable();metaMapper.createQueueTable();metaMapper.createBindingTable();System.out.println("[DataBaseManager] 创建表完成!");}/*** 添加默认交换机* RabbitMQ 有一个这样的设定:带有一个 匿名 的交换机,类型是 Direct*/private void createDefaultData() {Exchange exchange = new Exchange();exchange.setName("");exchange.setType(ExchangeType.DIRECT);exchange.setDurable(true);exchange.setAutoDelete(false);metaMapper.insertExchange(exchange);System.out.println("[DataBaseManager] 创建初始数据完成!");}//把数据库中其他操作也在这里封装一下public void insertExchange(Exchange exchange) {metaMapper.insertExchange(exchange);}public List<Exchange> selectAllExchange() {return metaMapper.selectAllExchange();}public void deleteExchange(String exchangeName) {metaMapper.deleteExchange(exchangeName);}public void insertQueue(MSGQueue queue) {metaMapper.insertQueue(queue);}public List<MSGQueue> selectAllQueue() {return metaMapper.selectAllQueue();}public void deleteQueue(String queueName) {metaMapper.deleteQueue(queueName);}public void insertBinding(Binding binding) {metaMapper.insertBinding(binding);}public List<Binding> selectAllBinding() {return metaMapper.selectAllBinding();}public void deleteBinding(Binding binding) {metaMapper.deleteBinding(binding);}public void deleteDB() {//删除文件File file = new File("./data/meta.db");boolean res = file.delete();if(res) {System.out.println("[DataBaseManager] 数据库文件删除完毕!");} else {System.out.println("[DataBaseManager] 数据库文件删除失败!");}//删除目录File dataDir = new File("./data");boolean ret = dataDir.delete();if(ret) {System.out.println("[DataBaseManager] 数据库目录删除完成!");} else {System.out.println("[DataBaseManager] 数据库目录删除失败!");}}}
1.5、针对 DataBaseManager 进行单元测试
设计单元测试,这里的要求就是单元测试用例和用例之间是需要相互独立的,不会干扰,例如以下情况:
测试过程中,向数据库中插入数据 a .
在针对 b 进行测试,可能 a 这里的数据会对 b 造成干扰.
Ps:这里不一定是数据库,也可能是其他方面,例如是否搞了一个文件,是否占用了端口...
@SpringBootTest
public class DataBaseManagerTests {private DataBaseManager dataBaseManager = new DataBaseManager();@BeforeEachpublic void setUp() {RabbitmqProjectApplication.context = SpringApplication.run(RabbitmqProjectApplication.class);dataBaseManager.init();}@AfterEachpublic void setclose() {//此处不能直接删除 数据库文件 ,需要先关闭 context 对象//此处 context 对象持有了 MetaMapper 的实例, MetaMapper 又打开了 meta.db 数据库//如果 meta.db 被别人打开了,此时删除文件是不会成功的(Windows 系统限制, Linux 则不会)//另一方面 context 会占用 8080 端口,此处的 close 也是释放 8080 端口RabbitmqProjectApplication.context.close();dataBaseManager.deleteDB();}@Testpublic void testInitTable() {List<Exchange> exchanges = dataBaseManager.selectAllExchange();List<MSGQueue> msgQueues = dataBaseManager.selectAllQueue();List<Binding> bindings = dataBaseManager.selectAllBinding();Assertions.assertEquals(1, exchanges.size());Assertions.assertEquals("", exchanges.get(0).getName());Assertions.assertEquals(ExchangeType.DIRECT, exchanges.get(0).getType());Assertions.assertEquals(0, msgQueues.size());Assertions.assertEquals(0, bindings.size());}private Exchange createTestExchange(String exchangeName) {Exchange exchange = new Exchange();exchange.setName(exchangeName);exchange.setType(ExchangeType.FANOUT);exchange.setDurable(true);exchange.setAutoDelete(false);exchange.setArguments("aaa", 1);exchange.setArguments("bbb", 2);return exchange;}@Testpublic void insertExhangeTest() {Exchange exchange = createTestExchange("testExchange");dataBaseManager.insertExchange(exchange);List<Exchange> exchanges = dataBaseManager.selectAllExchange();Assertions.assertEquals(2, exchanges.size());Exchange testExchange = exchanges.get(1);Assertions.assertEquals("testExchange", testExchange.getName());Assertions.assertEquals(ExchangeType.FANOUT, testExchange.getType());Assertions.assertEquals(true, testExchange.isDurable());Assertions.assertEquals(false, testExchange.isAutoDelete());Assertions.assertEquals(1, testExchange.getArguments("aaa"));Assertions.assertEquals(2, testExchange.getArguments("bbb"));}@Testpublic void deleteExchangeTest() {Exchange exchange = createTestExchange("testExchange");dataBaseManager.insertExchange(exchange);List<Exchange> exchanges = dataBaseManager.selectAllExchange();Assertions.assertEquals(2, exchanges.size());Assertions.assertEquals("testExchange", exchanges.get(1).getName());//删除dataBaseManager.deleteExchange("testExchange");exchanges = dataBaseManager.selectAllExchange();Assertions.assertEquals(1, exchanges.size());}private MSGQueue createTestQueue(String queueName) {MSGQueue queue = new MSGQueue();queue.setName(queueName);queue.setDurable(true);queue.setExclusive(false);queue.setAutoDelete(false);queue.setArguments("aaa", 1);queue.setArguments("bbb", 2);return queue;}@Testpublic void testInsertQueue() {MSGQueue queue = createTestQueue("testQueue");dataBaseManager.insertQueue(queue);List<MSGQueue> queues = dataBaseManager.selectAllQueue();Assertions.assertEquals(1, queues.size());MSGQueue msgQueue = queues.get(0);Assertions.assertEquals("testQueue", msgQueue.getName());Assertions.assertEquals(true, msgQueue.isDurable());Assertions.assertEquals(false, msgQueue.isExclusive());Assertions.assertEquals(false, msgQueue.isAutoDelete());Assertions.assertEquals(1, msgQueue.getArguments("aaa"));Assertions.assertEquals(2, msgQueue.getArguments("bbb"));}@Testpublic void testDeleteQueue() {MSGQueue queue = createTestQueue("testQueue");dataBaseManager.insertQueue(queue);List<MSGQueue> queues = dataBaseManager.selectAllQueue();Assertions.assertEquals(1, queues.size());//删除dataBaseManager.deleteQueue("testQueue");queues = dataBaseManager.selectAllQueue();Assertions.assertEquals(0, queues.size());}private Binding createTestBinding(String exchangeName, String queueName) {Binding binding = new Binding();binding.setExchangeName(exchangeName);binding.setQueueName(queueName);binding.setBindingKey("testBindingKey");return binding;}@Testpublic void testInsertAndDeleteBinding() {Binding binding = createTestBinding("testExchange", "testQueue");dataBaseManager.insertBinding(binding);List<Binding> bindingList = dataBaseManager.selectAllBinding();Assertions.assertEquals(1, bindingList.size());binding = bindingList.get(0);Assertions.assertEquals("testExchange", binding.getExchangeName());Assertions.assertEquals("testQueue", binding.getQueueName());Assertions.assertEquals("testBindingKey", binding.getBindingKey());//删除dataBaseManager.deleteBinding(binding);bindingList = dataBaseManager.selectAllBinding();Assertions.assertEquals(0, bindingList.size());}}
当然,我只是做了简单的设计测试用例,实际上站在更严谨的角度,还需要设计更丰富的测试用例~
相比于 功能/业务代码,测试用例代码编写起来虽然比较无聊,但是重要性是非常大的,这些操作会大大提高整个项目的开发效率.
Ps:单元测试,本来就是开发要搞的活,写代码不可能没有 bug,进行周密的测试,是应对 bug 最有效的手段.

相关文章:
根据源码,模拟实现 RabbitMQ - 通过 SQLite + MyBatis 设计数据库(2)
目录 一、数据库设计 1.1、数据库选择 1.2、环境配置 1.3、建库建表接口实现 1.4、封装数据库操作 1.5、针对 DataBaseManager 进行单元测试 一、数据库设计 1.1、数据库选择 MySQL 是我们最熟悉的数据库,但是这里我们选择使用 SQLite,原因如下&am…...
1、基于 CentOS 7 构建 LVS-DR 群集。 2、配置nginx负载均衡
一、基于CentOS7和、构建LVS-DR群集 准备四台虚拟机 ip作用192.168.27.150客户端192.168.27.151LVS192.168.27.152RS192.168.27.152RS 关闭防火墙 [rootlocalhost ~]# systemctl stop firewalld安装ifconfig yum install net-tools.x86_64 -y1、DS上 1.1 配置LVS虚拟IP …...
android 如何分析应用的内存(十七)——使用MAT查看Android堆
android 如何分析应用的内存(十七)——使用MAT查看Android堆 前一篇文章,介绍了使用Android profiler中的memory profiler来查看Android的堆情况。 如Android 堆中有哪些对象,这些对象的引用情况是什么样子的。 可是我们依然面临…...
Spring 使用注解储存对象
文章目录 前言存储 Bean 对象五大注解五大注解示例配置包扫描路径读取bean的示例 方法注解 Bean Bean 命名规则重命名 Bean 前言 通过在 spring-config 中添加bean的注册内容,我们已经可以实现基本的Spring读取和存储对象的操作了,但在操作中我们发现读…...
一、初始 Spring MVC
文章目录 一、回顾 MVC 模式二、初始 Spring MVC2.1 Spring MVC 核心组件2.1.1 前端控制器(DispatcherServlet)2.1.2 处理器映射器(HandlerMapping)2.1.3 处理器适配器(HandlerAdapter)2.1.3 后端控制器&am…...
《爬虫》爬取页面图片并保存
爬虫 前言代码效果 简单的爬取图片 前言 这几天打算整理与迁移一下博客。因为 CSDN 的 Markdown 编辑器很好用 ,所以全部文章与相关图片都保存在 CSDN。而且 CSDN 支持一键导出自己的文章为 markdown 文件。但导出的文件中图片的连接依旧是 url 连接。为了方便将图…...
【项目部署】JavaScript解析JSON解析报错Unexpected token xxx is not valid JSON
问题背景 这个报错发生在之前部署的一个前后端分离的项目中。后端使用的Spring Boot,前端使用的JavaScript,前后端交互使用Thymeleaf框架。 现象 项目组的另一个小伙伴说,突然有个页面打不开了,整个页面全空白。我F12打开浏览器…...
做接口测试如何上次文件
在日常工作中,经常有上传文件功能的测试场景,因此,本文介绍两种主流编写上传文件接口测试脚本的方法。 首先,要知道文件上传的一般原理:客户端根据文件路径读取文件内容,将文件内容转换成二进制文件流的格式…...
Java SPI机制详解-01
1. 概述 SPI(Service Provider Interface),是 Java 6 引入了一个内置功能,实现服务提供发现和加载机制,使之与特定接口的匹配。 SPI 机制的核心思想就是 解耦 ,将装配的控制权移到程序之外,这…...
由浅入深C系列六:C中实现字符串trim的功能
C中实现字符串trim的功能 简介设计思路代码实现运行效果 简介 一个项目中,需要用c语言实现对字符串中的字定字符进行过滤并从字符串的删除,查询了C语言的基本库,没有发现有这样的函数,于是发挥程序员的主观能力性,自力…...
博客网站添加复制转载提醒弹窗Html代码
网站如果是完全禁止右键(复制、另存为等)操作,对用户来说体验感会降低,但是又不希望自己的原创内容直接被copy,今天飞飞和你们分享几行复制转载提醒弹窗Html代码。 效果展示: 复制以下代码,将其…...
ubuntu下nfs服务安装
操作系统:ubuntu22.04.2 一、服务端安装与配置 1、在服务端安装nfs服务端组件 sudo apt install nfs-kernel-server 2、创建共享目录share并且授权所有人可以访问 sudo mkdir /shared sudo chmod -R 777 /shared 3、配置nfs sudo vim /etc/exports 这将允许…...
Unity框架学习--2
接上文 IOC 容器是一个很方便的模块管理工具。 除了可以用来注册和获取模块,IOC 容器一般还会有一个隐藏的功能,即: 注册接口模块 抽象-实现 这种形式注册和获取对象的方式是符合依赖倒置原则的。 依赖倒置原则(Dependence I…...
WebRTC音视频通话-实现GPUImage视频美颜滤镜效果iOS
WebRTC音视频通话-实现GPUImage视频美颜滤镜效果 在WebRTC音视频通话的GPUImage美颜效果图如下 可以看下 之前搭建ossrs服务,可以查看:https://blog.csdn.net/gloryFlow/article/details/132257196 之前实现iOS端调用ossrs音视频通话,可以查…...
82. 删除排序链表中的重复元素 II
题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台 解题思路:设置一个新的哑元节点result,作为头节点,将head中不重复地节点依次链接到哑元节点后面,最后返回result.next 初始值&…...
centos 7.x 单用户模式
最近碰到 centos 7.9 一些参数设置错误无法启动系统的情况,研究后可以使用单用户模式进入系统进行恢复操作。 进入启动界面,按 e ro 替换为 rw init/sysroot/bin/sh 替换前 替换后 Ctrl-x 进行重启进入单用户模式 执行 chroot /sysroot 可以查看日…...
取证--理论
资料: 各比赛 Writeup : https://meiyacup.cn/Mo_index_gci_36.html 哔站比赛复盘视频: https://space.bilibili.com/453117423?spm_id_from333.337.search-card.all.click 自动分析取证四部曲 新建案例添加设备自动取证制作报告 取证大…...
Tik Tok娱乐+电商MCN怎么做?
在美国外的热门市场中,TikTok 主要做的区域市场包括中东、拉美、欧洲和东亚,而这里面适合做电商的其实并不多。 欧洲、东亚都属于成熟市场,且 TikTok 本身在欧洲面临 DSA 法案更严格的审查,与在英国相同,欧洲各市场消…...
java 自定义xss校验注解实现
自定义一个注解Xss。名字随意 import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Targe…...
Selenium图片滑块验证码
因为种种原因没能实现愿景的目标,在这里记录一下中间结果,也算是一个收场吧。这篇文章主要是用selenium解决滑块验证码的个别案列。 思路: 用selenium打开浏览器指定网站 将残缺块图片和背景图片下载到本地 对比两张图片的相似地方&#…...
铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...
【网络】每天掌握一个Linux命令 - iftop
在Linux系统中,iftop是网络管理的得力助手,能实时监控网络流量、连接情况等,帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...
7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...
黑马Mybatis
Mybatis 表现层:页面展示 业务层:逻辑处理 持久层:持久数据化保存 在这里插入图片描述 Mybatis快速入门 
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...
服务器硬防的应用场景都有哪些?
服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...
Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...
从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...
docker 部署发现spring.profiles.active 问题
报错: org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...
C# 表达式和运算符(求值顺序)
求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如,已知表达式3*52,依照子表达式的求值顺序,有两种可能的结果,如图9-3所示。 如果乘法先执行,结果是17。如果5…...
