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

这篇具有很好参考价值的文章主要介绍了根据源码,模拟实现 RabbitMQ - 通过 SQLite + MyBatis 设计数据库(2)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

一、数据库设计

1.1、数据库选择

1.2、环境配置

1.3、建库建表接口实现

1.4、封装数据库操作

1.5、针对 DataBaseManager 进行单元测试

1.6、心得


一、数据库设计


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 中间件的设定).

根据源码,模拟实现 RabbitMQ - 通过 SQLite + MyBatis 设计数据库(2),RabbitMQ,数据库,sqlite,mybatis,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 最有效的手段.

1.6、心得

做好单元测试真的很重要,在 XXXMapper.xml 中,我在标签中设定了参数 parameterType,如下 

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

一启动项目就报错( 'sqlSessionFactory' 报错),原因就是 Exchange 中缺少 getter 方法~~

但实际上我并“没有缺少 getter 方法”,因为我再写完属性以后就直接用 IDEA 自动生成 getter/setter 方法了,但是!!! 我往数据库中插入 arguments 时约定为 varchar 类型,因此 setter/getter 方法的无论在 set ,还是 get 时,得到都是 Map<String, Object> 类型,因此项目一启动通过 MyBatis 加载 XXXMapper.xml 文件会直接报错!!!

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

正确的写法因该是写入/读出数据库,都因该序列化成 JSON 字符串的格式,如下:

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

 

根据源码,模拟实现 RabbitMQ - 通过 SQLite + MyBatis 设计数据库(2),RabbitMQ,数据库,sqlite,mybatis,rabbitmq文章来源地址https://www.toymoban.com/news/detail-646224.html

到了这里,关于根据源码,模拟实现 RabbitMQ - 通过 SQLite + MyBatis 设计数据库(2)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 根据源码,模拟实现 RabbitMQ - 转发规则实现(6)

    目录 一、转发规则实现 1.1、需求分析 1.2、实现 Router 转发规则 1.2.1、bindingKey 和 routingKey 参数校验 1.2.2、消息匹配规则 1.2.3、主题交换机匹配规则 这里主要实现 routingKey 和 bindingKey 参数的校验,以及 TopicExchange 类型绑定规则的实现. 这里重点来看一下 Topic 交换机的转发规则

    2024年02月12日
    浏览(43)
  • 根据源码,模拟实现 RabbitMQ - 内存数据管理(4)

    目录 一、内存数据管理 1.1、需求分析 1.2、实现 MemoryDataCenter 类 1.2.1、ConcurrentHashMap 数据管理 1.2.2、封装交换机操作 1.2.3、封装队列操作 1.2.4、封装绑定操作 1.2.5、封装消息操作 1.2.6、封装未确认消息操作 1.2.7、封装恢复数据操作 当前已经使用 数据库 管理了 交换机、绑定

    2024年02月11日
    浏览(41)
  • 根据源码,模拟实现 RabbitMQ - 从需求分析到实现核心类(1)

    目录 一、需求分析 1.1、对 Message Queue 的认识 1.2、消息队列核心概念 1.3、Broker Server 内部关键概念 1.4、Broker Server 核心 API (重点实现) 1.5、交换机类型 Direct 直接交换机 Fanout 扇出交换机 Topic 主题交换机 1.6、持久化 1.7、网络通信 通信流程 远程调用设计思想 1.8、模块设计图

    2024年02月12日
    浏览(43)
  • 根据源码,模拟实现 RabbitMQ - 实现消息持久化,统一硬盘操作(3)

    目录 一、实现消息持久化 1.1、消息的存储设定 1.1.1、存储方式 1.1.2、存储格式约定 1.1.3、queue_data.txt 文件内容  1.1.4、queue_stat.txt 文件内容 1.2、实现 MessageFileManager 类 1.2.1、设计目录结构和文件格式 1.2.2、实现消息的写入 1.2.3、实现消息的删除(随机访问文件) 1.2.4、获取队

    2024年02月12日
    浏览(50)
  • 简单模拟Mybatis通过注解查询SQL

    通过自定义注解,简单模拟Mybatis通过注解查询SQL。 首先,创建自定义注解@MyDao和@MySelect。 MyDao.java MySelect.java 创建UserDao,使用上面的自定义注解。 UserDao.java MyInterceptor.java 处理@MySelect注解的方法实现。 InterfaceDynamicRegister.java 处理@MyDao的注解的Mapper。 TestController.java 调用use

    2024年02月16日
    浏览(37)
  • 毕业设计——基于SpringBoot+Mybatis+Thymeleaf架构实现的员工管理系统(源码+数据库)

    完整项目地址:https://download.csdn.net/download/lijunhcn/88430276 SpringBoot整合CRUD实现员工管理案例,将Mybatis整合到原项目中,加入了数据库,添加了日期选项的控件。 环境要求 JDK8以上 IDEA MySQL8 Maven3 需要熟练掌握MySQL数据库,SpringBoot及MyBatis知识,简单的前端知识; 数据库环境 创建

    2024年02月03日
    浏览(50)
  • 【java毕业设计】基于javaEE+Mybatis的WEB仓库管理系统设计与实现(毕业论文+程序源码)——仓库管理系统

    大家好,今天给大家介绍基于javaEE+Mybatis的WEB仓库管理系统设计与实现,文章末尾附有本毕业设计的论文和源码下载地址哦。 需要下载开题报告PPT模板及论文答辩PPT模板等的小伙伴,可以进入我的博客主页查看左侧最下面栏目中的自助下载方法哦 文章目录: 仓库物品的管理

    2024年02月02日
    浏览(50)
  • 基于微信小程序的模拟考试小程序的设计与实现(源码+lw+部署文档+讲解等)

    💗 博主介绍 :✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌💗 👇🏻 精彩专栏 推荐订阅 👇🏻 2023-2024年最值得选的微信小程序毕业设

    2024年02月07日
    浏览(43)
  • 通过C实现sqlite3操作,(增删改查),导入电子词典

    一、插入 二、导入电子词典

    2024年02月12日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包