根据源码,模拟实现 RabbitMQ - 通过 SQLite + MyBatis 设计数据库(2)

news2024/11/25 15:43:55

目录

一、数据库设计

1.1、数据库选择

1.2、环境配置

1.3、建库建表接口实现

1.4、封装数据库操作

1.5、针对 DataBaseManager 进行单元测试


一、数据库设计


1.1、数据库选择

MySQL 是我们最熟悉的数据库,但是这里我们选择使用 SQLite,原因如下:

  1. SQLite 比 MySQL 更轻量:一个完整的 SQLite 数据库,只有一个单独的可执行文件(不到 1M).
  2. SQLite 操作简便:SQLite 只是一个本地数据库,相当于是直接操作本地的硬盘.
  3. 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.db
    username:
    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 类,来完成数据库相关的操作,注意细节如下:

  1. 初始化方法:一般谈到初始化,都会用到 构造方法,但是这里我们使用一个 普通的方法 —— init();构造方法一般是用来初始化类的属性,不会涉及到太多的业务逻辑,而此处的初始化,带有业务逻辑,还是单独领出来,手动来调用比较合适.
  2. 建库建表逻辑:这里期望,broker server 启动的时候做出如下逻辑判断:
    1. 如果数据库已经存在(表存在),不做任何操作.
    2. 如果数据库不存在,则建库建表,构造默认数据.

Ps:怎么判定数据库存在或者不存在?就判定 meta.db 文件是否存在即可(配置文件中的 url).

public class DataBaseManager {

    //这里不使用 Autowired 注解获取,因为当前这个类需要我们后面手动管理
    private MetaMapper metaMapper;

    //针对数据库进行初始化
    public void init() {
        //手动获取到 MetaMapper
        metaMapper = 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();

    @BeforeEach
    public void setUp() {
        RabbitmqProjectApplication.context = SpringApplication.run(RabbitmqProjectApplication.class);
        dataBaseManager.init();
    }

    @AfterEach
    public void setclose() {
        //此处不能直接删除 数据库文件 ,需要先关闭 context 对象
        //此处 context 对象持有了 MetaMapper 的实例, MetaMapper 又打开了 meta.db 数据库
        //如果 meta.db 被别人打开了,此时删除文件是不会成功的(Windows 系统限制, Linux 则不会)
        //另一方面 context 会占用 8080 端口,此处的 close 也是释放 8080 端口
        RabbitmqProjectApplication.context.close();
        dataBaseManager.deleteDB();
    }

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

    @Test
    public 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"));

    }

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

    @Test
    public 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"));
    }

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

    @Test
    public 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 最有效的手段.

 

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

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

相关文章

什么是多线程?进程和线程的区别是什么?如何使用Java实现多线程?

文章目录 前言我们为什么要使用线程而不是进程来实现并发编程什么是线程进程和线程的区别如何使用Java实现多线程创建线程1.创建一个继承 Thread 类的线程类2.实现 Runnable 接口匿名内部类方式实现 Runnable 接口lambda 表达式实现 Runnable 接口 Thread 类的常见构造方法Thre…

NO.1 MyBatis配置文件:配置连接数据库的环境,实现数据库连接

目录 1、MyBatis配置数据库环境的连接方式 1.1连接方式一&#xff1a;MyBatis核心配置文件配置数据库连接信息 1.2连接方式二&#xff1a;在MyBatis核心配置文件中引入properties文件&#xff0c;配置数据库的环境 2、MyBatisd核心配置文件连接数据库的环境完整配置信息 3…

基于Matlab实现心电信号小波特征提取和对应疾病识别仿真(附上源码+数据集)

本文基于Matlab平台&#xff0c;研究了心电信号的小波特征提取方法&#xff0c;并应用于心电信号疾病识别仿真实验中。首先&#xff0c;介绍了心电信号的基本特征和常见的心电疾病。然后&#xff0c;详细阐述了小波变换的原理和方法&#xff0c;并提出了一种基于小波分解和小波…

[Leetcode] [Tutorial] 多维动态规划(未完待续)

文章目录 62. 不同路径Solution 62. 不同路径 一个机器人位于一个 m ∗ * ∗ n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角。 问总共有多少条不同的路径&#xff1f; 示例…

线程转换状态,傻傻分不清等待和阻塞吗?你还在暴力的停止线程吗?

线程切换 线程创建之后&#xff0c;调用start()方法开始运行。当线程执行wait()方法之后&#xff0c;线程进入等待状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态&#xff0c;而超时等待状态相当于在等待状态的基础上增加了超时限制&#xff0c;也就是超…

StringJoiner

1、为什么要学习StringJoiner&#xff1f; 2、StringJoiner概述 StringJoiner跟StringBuilder一样&#xff0c;也可以看成一个容器&#xff0c;创建之后里面的内容是可变的。 2.1、作用 提高字符串的操作效率&#xff0c;而且代码编写特别简洁&#xff0c;但是目前市场上很少有…

用友时空KSOA SQL注入漏洞复现(HW0day)

0x01 产品简介 用友时空KSOA是建立在SOA理念指导下研发的新一代产品&#xff0c;是根据流通企业最前沿的I需求推出的统一的IT基础架构&#xff0c;它可以让流通企业各个时期建立的IT系统之间彼此轻松对话&#xff0c;帮助流通企业保护原有的IT投资&#xff0c;简化IT管理&#…

学习笔记整理-JS-03-表达式和运算符

[[toc]] 一、表达式和运算符 1. 表达式 表达式种类 算术、关系、逻辑、赋值、综合 二、JS基本表达式 1. 算术运算符 意义运算符加减-乘*除/取余% 加减乘除 加减的符号和数学一致&#xff0c;乘号是*号&#xff0c;除法是/号默认情况&#xff0c;乘除法的优先级高于加法和…

【软件工程】软件测试

软件测试的对象 软件程序文档 测试对象&#xff1a;各个阶段产生的源程序和文档。 软件测试的目的 基于不同的立场&#xff0c;对软件测试的目的存在着两种完全对立的观点。 &#xff08;1&#xff09;一种观点是通过测试暴露出软件中所包含的故障和缺陷(从用户的角度)&#xf…

ORB-SLAM2第一节---单目地图初始化

单目初始化 1.前提条件&#xff08;640*480&#xff09; 参与初始化的两帧各自的特征点数目都需要大于100.两帧特征点成功匹配的数目需要大于或等于100.两帧特征点三角化成功的三维点数目需要大于50. 2.针对条件三 流程如下 记录当前帧和参考帧&#xff08;第一帧&#xff…

计算机组成原理 汇编语言

..................................................

fastadmin采坑之页面调转

这里有个业务需求&#xff0c;就是一个表格页面添加一个报名按钮&#xff0c;这个报名按钮就对应另外一个表格页面的新增&#xff0c;那就不用单独写个报名页面了&#xff0c;直接添加一个报名按钮&#xff0c;然后按钮的url直接指向另外一个页面的新增页面&#xff0c;真的很方…

[vscode]vscode运行cmake时候exe不执行而且前面多一些字符

遇到一个奇怪问题,你单独打开cmd去执行vscode编译过程序没问题&#xff0c;但是你在vscode确不会执行&#xff0c;这是因为vscode没有读取到电脑环境变量导致加载DLL失败&#xff0c;但是在vscode终端不会给你提示少DLL&#xff0c;需要你自己把DLL复制到exe目录即可解决问题。…

CDH6.3应知应会

文章目录 1. CDH 简介1.1 CDH版本 2. CDH 集群的优势是什么&#xff1f;3. CDH 集群的部署方式有哪些&#xff1f;4. CDH 集群中如何进行故障排除和监控&#xff1f;5. 你有使用 CDH 部署集群的经验吗&#xff1f;6. CDH 集群如何实现高可用性&#xff1f;7. 在 CDH 集群中&…

Qt Bridge for Adobe Photoshop安装

*社区版没有这个文件夹无法安装&#xff0c;可以下载企业用的&#xff0c;然后给money&#xff0c;不多也就316刀&#xff0c;折合人民币2千多&#xff0c;或者试用10天。 ** B站找到的文章&#xff08;作者&#xff1a;原版英语 https://www.bilibili.com/read/cv17933098 出…

Redis进阶(4)——结合redis.conf配置文件深入理解 Redis两种数据持久化方案:RDB和AOF

目录 引出持久化方案RDBAOF Redis的持久化方案RDB如果采用docker stop关闭如果采用强制关闭 AOF参数设置混编方式的加载让aof进行重写 两种持久化方案的优缺点AOF优缺点RDB优势和劣势 总结 引出 1.Redis数据持久化的两种方式&#xff0c;RDB和AOF; 2.RDB采用二进制存储&#xf…

PHP8的字符串操作1-PHP8知识详解

字符串是php中最重要的数据之一&#xff0c;字符串的操作在PHP编程占有重要的地位。在使用PHP语言开发web项目的过程中&#xff0c;为了实现某些功能&#xff0c;经常需要对某些字符串进行特殊的处理&#xff0c;比如字符串的格式化、字符串的连接与分割、字符串的比较、查找等…

redis设置database 不生效剖析

设置database 不生效剖析 前言配置加载类问题commons-pool 对象池 主页传送门&#xff1a;&#x1f4c0; 传送 前言 事情是这样的 今天在拉取了同事的代码做redis缓存设置的时候&#xff0c;发现即使已经设置了database, 但是存数据的时候还是用的默认0数据库。这引起了我的好…

Java-类型和变量(基于C语言的补充)

一个简单的Java程序 args){ System.out.println("Hello,world"); } }通过上述代码&#xff0c;我们可以看到一个完整的Java程序的结构&#xff0c;Java程序的结构由如下三个部分组成&#xff1a; 1.源文件&#xff08;扩展名为*.java)&#xff1a;源文件带有类的定义…

通过金字塔模型拥抱企业数字化转型

企业要实施成功的数字化营销转型改革&#xff0c;需要践行如下金字塔模型&#xff0c;从上至下依次是认知&#xff0c;应用&#xff0c;流程和组织&#xff0c;我们一一来拆解。 01 认知层 处于最顶层的是认知层&#xff0c;数字化营销的认知没有建立&#xff0c;很难实施彻头…