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

目录

一、数据库设计

1.1、数据库选择

1.2、环境配置

1.3、建库建表接口实现

1.4、封装数据库操作

[1.5、针对 DataBaseManager 进行单元测试](#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 依赖引入即可(版本自行考虑)~

XML 复制代码
        <!-- https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc -->
        <dependency>
            <groupId>org.xerial</groupId>
            <artifactId>sqlite-jdbc</artifactId>
            <version>3.41.2.1</version>
        </dependency>

配置如下

XML 复制代码
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 的操作.

java 复制代码
@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);


}

对应的实现如下:

XML 复制代码
    <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).

java 复制代码
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:这里不一定是数据库,也可能是其他方面,例如是否搞了一个文件,是否占用了端口...

java 复制代码
@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 最有效的手段.

相关推荐
一 乐2 小时前
民宿|基于java的民宿推荐系统(源码+数据库+文档)
java·前端·数据库·vue.js·论文·源码
美林数据Tempodata3 小时前
大模型驱动数据分析革新:美林数据智能问数解决方案破局传统 BI 痛点
数据库·人工智能·数据分析·大模型·智能问数
野槐3 小时前
node.js连接mysql写接口(一)
数据库·mysql
Zzzone6834 小时前
PostgreSQL日常维护
数据库·postgresql
chxii4 小时前
1.13使用 Node.js 操作 SQLite
数据库·sqlite·node.js
冰刀画的圈4 小时前
修改Oracle编码
数据库·oracle
这个胖子不太裤4 小时前
Django(自用)
数据库·django·sqlite
麻辣清汤4 小时前
MySQL 索引类型及其必要性与优点
数据库·mysql
2501_915374355 小时前
Neo4j 图数据库安装教程(2024最新版)—— Windows / Linux / macOS 全平台指南
数据库·windows·neo4j