当前位置: 首页 > news >正文

【消息队列】数据库的数据管理

1. 数据库的选择

对于当前实现消息队列这样的一个中间件来说,具体要使用哪个数据库,是需要稍作考虑的,如果直接使用 MySQL 数据库也是能实现正常的功能,但是 MySQL 也是一个客户端服务器程序,也就意味着如果想在其他服务器上部署这个消息队列的项目,还得需要安装 MySQL,其实是不够轻量化的!!!

此处为了使用更方便,能将这里实现的消息队列单独使用,简化配置环境,于是采用的数据库是更轻量级的数据库,SQLite。

SQLite 应用非常的广泛,尤其是在一些性能不高的设备上使用数据库的首选,一个完整的 SQLite 数据库,只有一个单独的可执行文件,体量特别小,不到 1M,我们甚至只需要在 maven 中引入相关依赖就可以使用 MyBatis 操作数据库了。

对比 MySQL 来说,SQLite 只是一个本地的数据库,并不是一个客户端服务器结构的程序,而是相当于直接操作本地的硬盘文件。

在 pom.xml 中引入 SQLite:

<dependency><groupId>org.xerial</groupId><artifactId>sqlite-jdbc</artifactId><version>3.42.0.0</version>
</dependency>

application.yml 中配置 SQLite 和 MyBatis 匹配路径:

spring:datasource:url: jdbc:sqlite:./data/meta.dbusername:password:driver-class-name: org.sqlite.JDBCmybatis:mapper-locations: classpath:mapper/**Mapper.xml

mybatis 配置项的配置就不用说了,这里主要是了解 datasource 配置项里面的 url,这里的 url 就是 SQLite 把数据存储在当前硬盘的某个指定的文件中。

此处使用的是相对路径,如果是在 IDEA 中直接运行程序,此时的工作路径就是当前项目所在的路径,如果是通过 java -jar 方式运行程序,此时在哪个目录下执行的命令,哪个目录就是工作目录。

而且此处的 username 和 password 是不需要声明的,MySQL 是一个客户端服务器程序,就可能会有很多个客户端去访问它,而 SQLite 不是客户端服务器程序,只有本地主机才能访问了(数据库存储在本地)。

虽然 MySQL 和 SQLite 不太一样,但是它们同样可以使用 MyBatis 这样的框架来操作。

完整的 xml 依赖:

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.4</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter-test</artifactId><version>3.0.4</version><scope>test</scope></dependency><!-- https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc --><dependency><groupId>org.xerial</groupId><artifactId>sqlite-jdbc</artifactId><version>3.42.0.0</version></dependency></dependencies>

2. 要存储到数据库中的数据

其实对于要存储到数据库中的数据,在思维导图中已经写出来了,那为什么这些数据要放在数据库中存储?换句话来说,这些数据使用文件来存储不行吗?

对于需要存储到数据库中的数据有:

交换机,队列,绑定

为什么交换机与队列绑定不能使用文件存储呢?其实也行,只是在考虑效率问题方面和业务需求上的考虑,最终使用数据库存储,这里想象一下,前面提到的 BrokerServer 需要提供的 API 中,创建交换机,创建队列,创建绑定(根据交换机和队列是否持久化来判断绑定是否需要持久化),交换机和队列是可以选择是否持久化的,如果选择了持久化,才说明需要持久化,对于持久化的队列和交换机来说,也不需要反复的增删改查,因为在内存中,也会有一份这样的数据,此时既然内存中有,那为什么要走数据库查询呢?其实本质上队列,交换机,绑定的持久化,只需要在项目重新启动的时候,把数据库中持久化的数据恢复到内存中就可以了。所以只有当 BrokerServer 启动时会恢复数据库的数据到内存中(查询数据库),再者只会在新增队列,新增交换机,新增绑定(插入记录)时可能会操作数据库,其他时候,都是操作在操作内存中的数据的。

所以这样一来,选择数据库存储是完全够用的,但是存储消息为何要使用文件,不推荐使用数据库呢?这里在后面讲到消息存储时会详细讲解。

3. 设计实体类

这里需要先在 SpringBoot 启动类目录下创建一个 mqserver 目录,这个目录用来放 BrokerServer 需要用到的代码,接下来在 mqserver 目录下,在创建一个 core 目录,这里设计的实体类,也就是放在 core 目录下。
在这里插入图片描述

3.1 Exchange 实体类

对于交换机主要由这属性组成:

身份唯一标识:String name

交换机的类型:ExchangeType type

是否持久化:boolean durable

是否自动删除:boolean autoDelete

额外参数选项:Map<String , Object> arguments

对于上述的自动删除,和额外参数选项,此项目就不再进行处理,只是留有一个口子方便随时扩展。

对于这个交换机类型,此处是单独提拎出一个枚举类来表示:

package com.example.messagequeue.mqserver.core;public enum ExchangeType {DIRECT(0), // 直接交换机FANOUT(1), // 扇出交换机TOPIC(2);  // 主题交换机private final int type;private ExchangeType(int type) {this.type = type;}public int getType() {return type;}
}

对于 arguments 虽然不实现具体的功能,但是还是为了避免后续扩展时能顺利的保存到数据库中,此时就需要考虑,数据库中如何存储一个 Map

数据库本身是没有 Map 这样的类型供我们使用的,但是可以把 Map 转换成 json 字符串,在查询的时候,在把这个 json 字符串转换回 Map 就可以了。此处可以使用 ObjectMapper 这样的一个对象进行对 Java 的 json 字符串的序列化和反序列化。

既然这样的思路是可行的,问题来到如何让 MyBatis 框架帮我们存的时候把对象转序列化成 json,数据库中存 json 字符串,取的时候把 json 字符串反序列化成 Java 对象呢?

其实在 MyBatis 完成数据库操作的时候,会自动调用到对象的 getter 和 setter 方法。

当 MyBatis 往数据库中写数据时,就会调用对象的 getter 方法拿到属性的值再往数据库中写,当 MyBatis 从数据库中读数据的时候,就会调用对象的 setter 方法,把数据库中读到的结果设置到对象的属性中。

了解了 MyBatis 会这样操作后,我们只需要针对 arguments 参数的 getter,setter 方法做修改即可。

让 getter 方法返回一个 json 字符串,让 setter 方法形参接收一个 json 字符串就可以了。于是 arguments 的 getter 和 setter 就可以写成这样:

public String getArguments() {// 把当前的 arguments 转成 jsonObjectMapper objectMapper = new ObjectMapper();try {return objectMapper.writeValueAsString(arguments);} catch (JsonProcessingException e) {e.printStackTrace();}return "{}";
}public void setArguments(String argumentsJson) {// 数据库读到的 json 转换成对象ObjectMapper objectMapper = new ObjectMapper();try {this.arguments = objectMapper.readValue(argumentsJson, new TypeReference<HashMap<String, Object>>() {});} catch (JsonProcessingException e) {e.printStackTrace();}
}// 重载一下 arguments 的 getter 和 setter 方便后续使用
public Object getArguments(String key) {return arguments.get(key);
}
public void setArguments(Map<String, Object> arguments) {this.arguments = arguments;
}
public void setArguments(String key, Object value) {this.arguments.put(key, value);
}

Exchange 完整代码:

package com.example.messagequeue.mqserver.core;import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;import java.util.HashMap;
import java.util.Map;/*** 这个类表示交换机*/
public class Exchange {// 身份标识(唯一)private String name;// 交换机类型, DIRECT, FANOUT, TOPICprivate ExchangeType type = ExchangeType.DIRECT;//交换机是否要持久化存储private boolean durable = false;// 没人使用时是否自动删除private boolean autoDelete = false;// 创建交换机时指定的一些额外的参数选项private Map<String , Object> arguments = new HashMap<>();public String getName() {return name;}public void setName(String name) {this.name = name;}public ExchangeType getType() {return type;}public void setType(ExchangeType type) {this.type = type;}public boolean isDurable() {return durable;}public void setDurable(boolean durable) {this.durable = durable;}public boolean isAutoDelete() {return autoDelete;}public void setAutoDelete(boolean autoDelete) {this.autoDelete = autoDelete;}public Object getArguments(String key) {return arguments.get(key);}public String getArguments() {// 把当前的 arguments 转成 jsonObjectMapper objectMapper = new ObjectMapper();try {return objectMapper.writeValueAsString(arguments);} catch (JsonProcessingException e) {e.printStackTrace();}return "{}";}public void setArguments(String key, Object value) {this.arguments.put(key, value);}public void setArguments(String argumentsJson) {// 数据库读到的 json 转换成对象ObjectMapper objectMapper = new ObjectMapper();try {this.arguments = objectMapper.readValue(argumentsJson,new TypeReference<HashMap<String, Object>>() {});} catch (JsonProcessingException e) {e.printStackTrace();}}public void setArguments(Map<String, Object> arguments) {this.arguments = arguments;}
}

ExchangeType 类完整代码:

package com.example.messagequeue.mqserver.core;public enum ExchangeType {DIRECT(0),FANOUT(1),TOPIC(2);private final int type;private ExchangeType(int type) {this.type = type;}public int getType() {return type;}
}

3.2 MsgQueue 实体类

对于队列目前主要由这属性组成:

队列唯一标识:String name

是否持久化:boolean durable

是否只能被一个消费者使用:boolean exclusive

自动删除:boolean autoDelete

扩展参数:Map<String, Object> arguments

这里自动删除和扩展参数,也是本项目中留有扩展接口暂不实现,而 exclusive 参数,是否独有,则留到彩蛋部分。

MsgQueue 这里也没什么好说的,主要也是 arguments 这个参数的 getter 和 setter 需要注意一下。

MsgQueue 完整代码:

package com.example.messagequeue.mqserver.core;import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;import java.util.HashMap;
import java.util.Map;/*** 这个类表示存储消息的队列*/
public class MsgQueue {// 队列的身份标识private String name;// 队列是否持久化private boolean durable = false;// 如果为 true 表示这个队列只能被一个消费者使用 nprivate boolean exclusive = false;// 自动删除 nprivate boolean autoDelete = false;// 扩展参数 nprivate Map<String, Object> arguments = new HashMap<>();public String getName() {return name;}public void setName(String name) {this.name = name;}public boolean isDurable() {return durable;}public void setDurable(boolean durable) {this.durable = durable;}public boolean isExclusive() {return exclusive;}public void setExclusive(boolean exclusive) {this.exclusive = exclusive;}public boolean isAutoDelete() {return autoDelete;}public void setAutoDelete(boolean autoDelete) {this.autoDelete = autoDelete;}public Object getArguments(String key) {return arguments.get(key);}public String getArguments() {// 把当前的 arguments 转成 jsonObjectMapper objectMapper = new ObjectMapper();try {return objectMapper.writeValueAsString(arguments);} catch (JsonProcessingException e) {e.printStackTrace();}return "{}";}public void setArguments(String key, Object value) {this.arguments.put(key, value);}public void setArguments(String argumentsJson) {// 数据库读到的 json 转换成对象ObjectMapper objectMapper = new ObjectMapper();try {this.arguments = objectMapper.readValue(argumentsJson, new TypeReference<HashMap<String, Object>>() {});} catch (JsonProcessingException e) {e.printStackTrace();}}public void setArguments(Map<String, Object> arguments) {this.arguments = arguments;}
}

当然上述代码其实还并不是最终代码,随着项目往后写代码,根据需求的到来也要需要进行一定的扩展。

3.3 Binding 实体类

对于绑定目前主要由这属性组成:

绑定的交换机名:String exchangeName

绑定的队列名:String queueName

绑定的匹配Key:String bindingKey

绑定实体类比较简单,bindingKey 的作用在前面章节也提到过,这里就不多介绍了,Binding 没有主键的原因是要依赖于 exchangeName 和 queueName 这两个维度来进行筛选,实体类主要是映射数据库中的数据,所以其实并不复杂,不涉及到业务,所以也就不做赘述。

Binding 完整代码:

package com.example.messagequeue.mqserver.core;/*** 表示队列和交换机之间的关联关系*/
public class Binding {private String exchangeName;private String queueName;// 题目private String bindingKey;public String getExchangeName() {return exchangeName;}public void setExchangeName(String exchangeName) {this.exchangeName = exchangeName;}public String getQueueName() {return queueName;}public void setQueueName(String queueName) {this.queueName = queueName;}public String getBindingKey() {return bindingKey;}public void setBindingKey(String bindingKey) {this.bindingKey = bindingKey;}
}

4. 建表操作

实体类写好了,剩下的就是创建数据库了,对于之前的 MySQL 来说,创建一个表需要先创建一个库,create databases …,然后在 create table …,然后把写好的 SQL 放在一个 db.sql 中,然后把这个 .sql 文件或者把这个文件的内容放在 MySQL 复制粘贴一执行就行了。之前这样做确实没问题,因为这样的项目大概部署一次就够了,不会反复操作,但是这里实现的消息队列,可能会设计到多次部署,比如多个服务器都想部署。

这里有没有一种方法,通过代码来自动的完成建库建表的操作呢?

其实 MyBatis 就能做到,只是之前 xml 来实现数据库的增删改查,对应的就是不同的 xml 标签,对于 create table 这样的语句有对应的标签提供吗?

没有!!!但是可以使用 update 标签来代替,update 标签中也可以写 create 语句,把每个建表的语句,都使用一个 update 标签,并对应一个 Java 方法。能否在一个 update 标签中一次性创建多张表呢?是不行的,当一个 update 标签写了多个 create table 的时候,只有第一个语句能执行。所以这里只能采取一个 Java 方法对应一个 xml 的建表的标签。

看到这,可能有个疑问,库呢?只提到建表,难道不用建立 databases 吗?前面在 yml 中配置的:

spring:datasource:url: jdbc:sqlite:./data/meta.dbusername:password:driver-class-name: org.sqlite.JDBC

meta.db 这个文件,本质上就是此项目用到的库,咱们在代码中只需要写创建表的语句就可以了。

现在,就按照上述说的来做:
在这里插入图片描述
在 mqserver 目录下建立 mapper 目录,这里面放着一个接口为 MetaMapper.java,对应的 resources 目录下的 mapper 目录里面的 MetaMapper.xml 就是对应上述 MetaMapper.java 接口里面方法的实现。这个很基本的 MyBatis 操作了,也就不再赘述。

基本标签:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.messagequeue.mqserver.mapper.MetaMapper"></mapper>

先是建表操作,对于这个数据库来说,应该有三张表,分别是 exchange,queue,binding。

void createExchangeTable();
void createQueueTable();
void createBindingTable();
<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>

5. 数据操作

接下来是增删改查,但是此项目不提供修改,其实也不太会去修改。

接下来就应该实现如下的 SQL 操作了:

插入一个交换机,查找所有的交换机,删除一个交换机

插入一个队列,查找所有的队列,删除一个队列

插入一个绑定,查找所有的绑定,删除一个绑定

为什么不设计一个方法,根据交换机名,或者队列名,查找交换机和队列呢?设置设计一个方法根据交换机名+队列名查找绑定呢?

前文提到过,由于这里对于 Exchange,Queue,Binding 的持久化,就是为了在项目启动的时候,将这些数据库硬盘上的数据恢复到内存中,项目运行起来了,启动了后,那个时候的查找其实就是去内存中查找了,正如思维导图所述,对于 Exchange,Queue,Binding,在内存中也会持有一份,而硬盘中是否持有,取决于客户端的选择了。

List<Exchange> selectAllExchanges();void deleteExchange(String exchangeName);void insertQueue(MsgQueue msgQueue);List<MsgQueue> selectAllQueues();void deleteQueue(String queueName);void insertBinding(Binding binding);List<Binding> selectAllBindings();void deleteBinding(Binding binding);
<insert id="insertExchange" parameterType="com.example.messagequeue.mqserver.core.Exchange">insert into exchange values (#{name}, #{type}, #{durable}, #{autoDelete}, #{arguments})
</insert><select id="selectAllExchanges" resultType="com.example.messagequeue.mqserver.core.Exchange">select * from exchange
</select><insert id="insertQueue" parameterType="com.example.messagequeue.mqserver.core.MsgQueue">insert into queue values(#{name}, #{durable}, #{exclusive}, #{autoDelete}, #{arguments})
</insert><select id="selectAllQueues" resultType="com.example.messagequeue.mqserver.core.MsgQueue">select * from queue
</select><insert id="insertBinding" parameterType="com.example.messagequeue.mqserver.core.Binding">insert into binding values(#{exchangeName}, #{queueName}, #{bindingKey})
</insert><select id="selectAllBindings" resultType="com.example.messagequeue.mqserver.core.Binding">select * from binding
</select><delete id="deleteExchange" parameterType="java.lang.String">delete from exchange where name = #{exchangeName}
</delete><delete id="deleteQueue" parameterType="java.lang.String">delete from queue where name = #{queueName}
</delete><delete id="deleteBinding" parameterType="com.example.messagequeue.mqserver.core.Binding">delete from binding where exchangeName = #{exchangeName} and queueName = #{queueName}
</delete>

完整代码如下:

MetaMapper.java 完整代码:

package com.example.messagequeue.mqserver.mapper;import com.example.messagequeue.mqserver.core.Binding;
import com.example.messagequeue.mqserver.core.Exchange;
import com.example.messagequeue.mqserver.core.MsgQueue;
import org.apache.ibatis.annotations.Mapper;import java.util.List;/*** 源属性*/
@Mapper
public interface MetaMapper {// 建表方法void createExchangeTable();void createQueueTable();void createBindingTable();// 插入删除查找操作void insertExchange(Exchange exchange);List<Exchange> selectAllExchanges();void deleteExchange(String exchangeName);void insertQueue(MsgQueue msgQueue);List<MsgQueue> selectAllQueues();void deleteQueue(String queueName);void insertBinding(Binding binding);List<Binding> selectAllBindings();void deleteBinding(Binding binding);
}

MetaMapper.xml 完整代码:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.messagequeue.mqserver.mapper.MetaMapper"><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.messagequeue.mqserver.core.Exchange">insert into exchange values (#{name}, #{type}, #{durable}, #{autoDelete}, #{arguments})</insert><select id="selectAllExchanges" resultType="com.example.messagequeue.mqserver.core.Exchange">select * from exchange</select><insert id="insertQueue" parameterType="com.example.messagequeue.mqserver.core.MsgQueue">insert into queue values(#{name}, #{durable}, #{exclusive}, #{autoDelete}, #{arguments})</insert><select id="selectAllQueues" resultType="com.example.messagequeue.mqserver.core.MsgQueue">select * from queue</select><insert id="insertBinding" parameterType="com.example.messagequeue.mqserver.core.Binding">insert into binding values(#{exchangeName}, #{queueName}, #{bindingKey})</insert><select id="selectAllBindings" resultType="com.example.messagequeue.mqserver.core.Binding">select * from binding</select><delete id="deleteExchange" parameterType="java.lang.String">delete from exchange where name = #{exchangeName}</delete><delete id="deleteQueue" parameterType="java.lang.String">delete from queue where name = #{queueName}</delete><delete id="deleteBinding" parameterType="com.example.messagequeue.mqserver.core.Binding">delete from binding where exchangeName = #{exchangeName} and queueName = #{queueName}</delete></mapper>

6. 整合数据库操作

这一小节的操作,就是将上述的建表以及数据的操作整合到一个类中(DataBaseManager),后续直接使用这个类去操作数据库。

对于第一个,就是先需要初始化,也就是先建立三张需要的表,和一些基本数据,初始化数据库就在 DataBaseManager 中写一个 init() 方法,用于初始化数据库。

要想操作数据库,也就是调用前面创建的 MetaMapper 里面的方法,这里由于是 SpringBoot 的项目,这里需要手动拿到 metaMapper,可以使用注解等等,但这里采取使用 Spring 应用上下文 ApplicationContext 当中获取 metaMapper 对象就可以了。只需要将启动类修改成如下:

package com.example.messagequeue;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;@SpringBootApplication
public class MessageQueueApplication {public static ConfigurableApplicationContext context;public static void main(String[] args) {context = SpringApplication.run(MessageQueueApplication.class, args);}}

后续通过 MessageQueueApplication.context 也是可以获取到想要的 Bean 实例。

6.1 初始化数据库

public class DataBaseManager {// 手动拿到 metaMapperprivate MetaMapper metaMapper;// 数据库初始化public void init() {// 获取到 MetaMappermetaMapper = MyMessageQueueApplication.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() {// 不需要手动创建 meta.db// 首次执行这里的数据库操作时, 就会自动创建出 meta.db 文件 (mybatis 完成的)metaMapper.createExchangeTable();metaMapper.createQueueTable();metaMapper.createBindingTable();System.out.println("[DataBaseManager] 创建表完成!");}private void createDefaultData() {// 添加一个默认的交换机// RabbitMQ 设定, 有一个匿名的交换机, 类型是 DIRECT.Exchange exchange = new Exchange();exchange.setName("");exchange.setType(ExchangeType.DIRECT);exchange.setDurable(true);exchange.setAutoDelete(false);metaMapper.insertExchange(exchange);System.out.println("[DataBaseManager] 创建初始数据完成!");}
}

6.2 封装操作数据的方法

// 提供方便单元测试时收尾工作要删除数据库的方法
public void deleteDB() {File file = new File("./data/meta.db");boolean ret = file.delete();if (ret) {System.out.println("[DataBaseManager] 删除数据库文件成功!");} else {System.out.println("[DataBaseManager] 删除数据库文件失败!");}File dataDir = new File("./data");ret = dataDir.delete();if (ret) {System.out.println("[DataBaseManager] 删除数据库目录成功!");} else {System.out.println("[DataBaseManager] 删除数据库目录失败!");}
}public void insertExchange(Exchange exchange) {metaMapper.insertExchange(exchange);
}public List<Exchange> selectAllExchanges() {return metaMapper.selectAllExchanges();
}public void deleteExchange(String exchangeName) {metaMapper.deleteExchange(exchangeName);
}public void insertQueue(MsgQueue msgQueue) {metaMapper.insertQueue(msgQueue);
}public List<MsgQueue> selectAllQueues() {return metaMapper.selectAllQueues();
}public void deleteQueue(String queueName) {metaMapper.deleteQueue(queueName);
}public void insertBinding(Binding binding) {metaMapper.insertBinding(binding);
}public List<Binding> selectAllBindings() {return metaMapper.selectAllBindings();
}public void deleteBinding(Binding binding) {metaMapper.deleteBinding(binding);
}

上述封装操作数据库的方法很简单,本质就是调用了下 metaMapper 里面的方法。

6.3 单元测试

这里详细的单元测试就不写了,相信写过 Spring 项目的都会进行单元测试,那么此处只给出一个标准测试 DataBaseManager 类的架子就行了,按照其中的一个测试方法接着往下写新的测试用例就OK了。

package com.example.messagequeue;import com.example.messagequeue.mqserver.core.Binding;
import com.example.messagequeue.mqserver.core.Exchange;
import com.example.messagequeue.mqserver.core.ExchangeType;
import com.example.messagequeue.mqserver.core.MsgQueue;
import com.example.messagequeue.mqserver.datacenter.DataBaseManager;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.test.context.SpringBootTest;import java.util.List;@SpringBootTest
public class DataBaseManagerTests {private DataBaseManager dataBaseManager = new DataBaseManager();/*** 准备工作*/@BeforeEachprivate void setUp() {MessageQueueApplication.context = SpringApplication.run(MessageQueueApplication.class);dataBaseManager.init();}/*** 收尾工作*/@AfterEachprivate void tearDown() {// 删除数据库// 为什么要先关闭 context 对象呢?// 此处的 context 对象持有了 MetaMapper 的示例, MataMapper 实例又打开了 meta.db 数据库文件// 另一方面, 获取 context 操作, 会占用 8080 端口MessageQueueApplication.context.close();dataBaseManager.deleteDB();}@Testpublic void testInitTable() {// 由于 init 方法在上面 setUp中调用过了, 在下面代码直接检查数据库状态即可// 查交换机表, 里面应该有一个数据List<Exchange> exchangeList = dataBaseManager.selectAllExchanges();List<MsgQueue> msgQueueList = dataBaseManager.selectAllQueues();List<Binding> bindingList = dataBaseManager.selectAllBindings();Assertions.assertEquals(1, exchangeList.size());Assertions.assertEquals("", exchangeList.get(0).getName());Assertions.assertEquals(ExchangeType.DIRECT, exchangeList.get(0).getType());Assertions.assertEquals(0, msgQueueList.size());Assertions.assertEquals(0, bindingList.size());}private Exchange createTestExchange(String exchangeName) {Exchange exchange = new Exchange();exchange.setName(exchangeName);exchange.setType(ExchangeType.FANOUT);exchange.setAutoDelete(false);exchange.setDurable(true);exchange.setArguments("aaa", 1);exchange.setArguments("bbb", 2);return exchange;}@Testpublic void testInsertExchange() {Exchange exchange = createTestExchange("testExchange");dataBaseManager.insertExchange(exchange);List<Exchange> exchangeList = dataBaseManager.selectAllExchanges();Assertions.assertEquals(2, exchangeList.size());Exchange newExchange = exchangeList.get(1);Assertions.assertEquals("testExchange", newExchange.getName());Assertions.assertEquals(ExchangeType.FANOUT, newExchange.getType());Assertions.assertEquals(false, newExchange.isAutoDelete());Assertions.assertEquals(true, newExchange.isDurable());Assertions.assertEquals(1, newExchange.getArguments("aaa"));Assertions.assertEquals(2, newExchange.getArguments("bbb"));}// ......

相关文章:

【消息队列】数据库的数据管理

1. 数据库的选择 对于当前实现消息队列这样的一个中间件来说&#xff0c;具体要使用哪个数据库&#xff0c;是需要稍作考虑的&#xff0c;如果直接使用 MySQL 数据库也是能实现正常的功能&#xff0c;但是 MySQL 也是一个客户端服务器程序&#xff0c;也就意味着如果想在其他服…...

玩转ChatGPT:GPT 深入研究功能

一、写在前面 民间总结&#xff1a; 理科看Claude 3.7 Sonnet 文科看DeepSeek-R1 那么&#xff0c;ChatGPT呢&#xff1f; 看Deep Research&#xff08;深入研究&#xff09;功能。 对于科研狗来说&#xff0c;在这个文章爆炸的时代&#xff0c;如何利用AI准确、高效地收…...

Centos8部署mongodb报错记录

使用mongo ops安装mongodb6.0.4副本集报错 error while loading shared libraries: libnetsnmpmibs.so.35: cannot open shared object file: No such file or directory 解决 yum install net-snmp net-snmp-devel -y建议&#xff1a;初始化系统时把官网上的依赖包都装一遍 即…...

2024四川大学计算机考研复试上机真题

2024四川大学计算机考研复试上机真题 2024四川大学计算机考研复试机试真题 历年四川大学计算机考研复试机试真题 在线评测&#xff1a;https://app2098.acapp.acwing.com.cn/ 分数求和 题目描述 有一分数序列&#xff1a; 2/1 3/2 5/3 8/5 13/8 21/13… 求出这个数列的前 …...

前端面试题 口语化复述解答(从2025.3.8 开始频繁更新中)

背景 看了很多面试题及其答案。但是过于标准化&#xff0c;一般不能直接用于回复面试官&#xff0c;这里我将总结一系列面试题&#xff0c;用于自我复习也乐于分享给大家&#xff0c;欢迎大家提供建议&#xff0c;我必不断完善之。 Javascript ES6 1. var let const 的区别…...

更多文章请查看

更多文章知识请移步至下面链接&#xff0c;期待你的关注 如需查看新文章&#xff0c;请前往&#xff1a; 博主知识库https://www.yuque.com/xinzaigeek...

vulnhub靶场之【digitalworld.local系列】的vengeance靶机

前言 靶机&#xff1a;digitalworld.local-vengeance&#xff0c;IP地址为192.168.10.10 攻击&#xff1a;kali&#xff0c;IP地址为192.168.10.6 kali采用VMware虚拟机&#xff0c;靶机选择使用VMware打开文件&#xff0c;都选择桥接网络 这里官方给的有两种方式&#xff…...

MySql的安装及数据库的基本操作命令

1.MySQL的安装 1.1进入MySQL官方网站 1.2点击下载 1.3下拉选择MySQL社区版 1.4选择你需要下载的版本及其安装的系统和下载方式 直接安装以及压缩包 建议选择8.4.4LST LST表明此版本为长期支持版 新手建议选择红框勾选的安装方式 1.5 安装包下载完毕之后点击安装 2.数据库…...

中性点直接接地电网接地故障Simulink仿真

1.模型简介 本仿真模型基于MATLAB/Simulink&#xff08;版本MATLAB 2017Ra&#xff09;软件。建议采用matlab2017 Ra及以上版本打开。&#xff08;若需要其他版本可联系代为转换&#xff09; 2.系统仿真图&#xff1a; 3.中性点直接接地电网接地故障基本概念&#xff08;本仿…...

Linux16-数据库、HTML

数据库&#xff1a; 数据存储&#xff1a; 变量、数组、链表-------------》内存 &#xff1a;程序运行结束、掉电数据丢失 文件 &#xff1a; 外存&#xff1a;程序运行结束、掉电数据不丢失 数据库&#xff1a; …...

SpireCV荣获Gitee 最有价值开源项目称号

什么是GVP&#xff1f; GVP全称Gitee Valuable Project&#xff0c;意思为Gitee最有价值开源项目。作为GVP称号的获得者&#xff0c;SpireCV在开源社区中展现出了卓越的实力和影响力&#xff0c;为开源软件的发展和推广做出了积极的贡献。 这一荣誉不仅充分肯定了过去阿木实验…...

open-webui+deepseek api实现deepseek自由

虽然deepseek是免费的&#xff0c;但是官网的体验感太差。 所以才会有某种想法&#xff0c;想要更加舒服的使用deepseek。 1.Python虚拟环境 这个我就不多赘述&#xff0c;切记Python版本一定要和open-webui制定的版本一致。 2.deepseek api 去这个网站充点钱&#xff08;…...

Hadoop八股

Hadoop八股 HadoopMapReduce考点MR on Yarn 分布式工作原理shuffle&#xff1a;MapTask 和 ReduceTask的处理流程MR中的MapTask 和 ReduceTask 的数量决定MR和Spark两者Shuffle的区别简单讲一下map- reduce 原理**MapReduce 的核心概念****MapReduce 的工作流程****MapReduce 的…...

.NET Core全屏截图,C#全屏截图

.NET Core全屏截图&#xff0c;C#全屏截图 使用框架&#xff1a; WPF.NET 8 using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.Linq; using System.Text; using System.Threading.Tasks; using System.W…...

ajax之生成一个ajax的demo示例

目录 一. node.js和express ​二. 使用express创建后端服务 三. 创建前端 一. node.js和express ajax是前端在不刷新的情况下访问后端的技术&#xff0c;所以首先需要配置一个后端服务&#xff0c;可以使用node.js和express。 首先生成一个空项目&#xff0c;新建main目录…...

软件工程笔记下

从程序到软件☆ 章节 知识点 概论☆ 软件的定义&#xff0c;特点&#xff0c;生存周期。软件工程的概论。软件危机。 1.☆软件&#xff1a;软件程序数据文档 &#xff08;1&#xff09;软件&#xff1a;是指在计算机系统的支持下&#xff0c;能够完成特定功能与性能的包括…...

【自学笔记】Numpy基础知识点总览-持续更新

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 Numpy基础知识点总览目录1. 简介Numpy是什么为什么使用Numpy 2. 数组对象&#xff08;ndarray&#xff09;创建数组数组的属性数组的形状操作 3. 数组的基本操作数组…...

大模型gpt结合drawio绘制流程图

draw下载地址 根据不同操作系统选择不同的安装 截图给gpt 并让他生成drawio格式的&#xff0c;选上推理 在本地将生成的内容保存为xml格式 使用drawio打开 保存的xml文件 只能说效果一般。...

3.8【Q】cv

这个draw_line函数的逻辑和功能是什么&#xff1f;代码思路是什么&#xff1f;怎么写的&#xff1f; 这个t是什么&#xff1f;t.v[0]和t.v[1],[2]又是什么&#xff1f; void rst::rasterizer::draw(rst::pos_buf_id pos_buffer, rst::ind_buf_id ind_buffer, rst::Primitive ty…...

STM32F10XXX标准库函数及外设结构体

时钟 APB1 void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState)&#xff1a;使能或失能 APB1 时钟 参数 可赋值 描述 RCC_APB1Periph RCC_APB1Periph_TIM2 RCC_APB1Periph_TIM3 RCC_APB1Periph_TIM4 RCC_APB1Periph_TIM5 RCC_APB1Peri…...

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...

在软件开发中正确使用MySQL日期时间类型的深度解析

在日常软件开发场景中&#xff0c;时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志&#xff0c;到供应链系统的物流节点时间戳&#xff0c;时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库&#xff0c;其日期时间类型的…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘

美国西海岸的夏天&#xff0c;再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至&#xff0c;这不仅是开发者的盛宴&#xff0c;更是全球数亿苹果用户翘首以盼的科技春晚。今年&#xff0c;苹果依旧为我们带来了全家桶式的系统更新&#xff0c;包括 iOS 26、iPadOS 26…...

label-studio的使用教程(导入本地路径)

文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...

PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建

制造业采购供应链管理是企业运营的核心环节&#xff0c;供应链协同管理在供应链上下游企业之间建立紧密的合作关系&#xff0c;通过信息共享、资源整合、业务协同等方式&#xff0c;实现供应链的全面管理和优化&#xff0c;提高供应链的效率和透明度&#xff0c;降低供应链的成…...

抖音增长新引擎:品融电商,一站式全案代运营领跑者

抖音增长新引擎&#xff1a;品融电商&#xff0c;一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中&#xff0c;品牌如何破浪前行&#xff1f;自建团队成本高、效果难控&#xff1b;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...

渲染学进阶内容——模型

最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...

C++.OpenGL (10/64)基础光照(Basic Lighting)

基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...

Element Plus 表单(el-form)中关于正整数输入的校验规则

目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入&#xff08;联动&#xff09;2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...

Linux 内存管理实战精讲:核心原理与面试常考点全解析

Linux 内存管理实战精讲&#xff1a;核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用&#xff0c;还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...