【消息队列】数据库的数据管理
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. 数据库的选择 对于当前实现消息队列这样的一个中间件来说,具体要使用哪个数据库,是需要稍作考虑的,如果直接使用 MySQL 数据库也是能实现正常的功能,但是 MySQL 也是一个客户端服务器程序,也就意味着如果想在其他服…...
pytest中pytest.ini文件的使用
pytest.ini 是 pytest 测试框架的配置文件,它允许你自定义 pytest 的行为。通过在 pytest.ini 中设置各种选项,可以改变测试用例的发现规则、输出格式、插件行为等。以下详细介绍 pytest.ini 文件的使用。 1. 文件位置 pytest.ini 文件通常位于项目的根目录下,pytest 在运…...
docker学习笔记(1)从安装docker到使用Portainer部署容器
docker学习笔记第一课 先交代背景 docker宿主机系统:阿里云ubuntu22.04 开发机系统:win11 docker镜像仓库:阿里云,此阿里云与宿主机系统没有关系,是阿里云提供的一个免费的docker仓库 代码托管平台:github&…...
Vue.js侦听器
侦听器 基本示例 计算属性允许我们声明性地计算衍生值。然而在有些情况下,我们需要在状态变化时执行一些“副作用”:例如更改 DOM,或是根据异步操作的结果去修改另一处的状态。 在组合式 API 中,我们可以使用 watch 函数在每次响应式状态发生变化时触发回调函数: …...
【C++学习篇】智能指针
目录 1. 智能指针的使用场景分析 2. RAII和智能指针的设计思路 3. C标准库智能指针的使用 4.shared_ptr和weak_ptr 4.1shared_ptr的循环引用问题 4.2 weak_ptr 1. 智能指针的使用场景分析 下⾯程序中我们可以看到,new了以后,我们也delete了,…...
数字电子技术基础(二十四)——TTL门电路的高、低电平的输出特性曲线
目录 1 TTL门电路的特性曲线 1.1 高电平输出特性 1.1.2 高电平输出特性的实验过程 1.1.2 TTL门电路的输出特性的实验结果 1.2 低电平的输出特性 1 TTL门电路的特性曲线 1.1 高电平输出特性 1.1.2 高电平输出特性的实验过程 现在想要测试TTL门电路的输出特性,…...
linux进程通信之共享内存
在 Linux 系统中,共享内存(Shared Memory) 是一种高效的进程间通信(IPC)方式,允许多个进程直接访问同一块物理内存区域。以下是关于 Linux 共享内存的详细讲解: 一、共享内存的核心特点 高速通信…...
学习第十一天-树
一、树的基础概念 1. 定义 树是一种非线性数据结构,由 n 个有限节点组成层次关系集合。特点: 有且仅有一个根节点其余节点分为若干互不相交的子树节点间通过父子关系连接 2. 关键术语 术语定义节点包含数据和子节点引用的单元根节点树的起始节点&#…...
场景题:10亿QQ用户,如何统计在线人数?
现在卷的环境下,面试除了八股文算法项目外,场景题也是问的越来越多了。一方面是就业市场竞争者较多所带来的必然结果;另一方面是公司对于应聘者的技术要求也越来越高了。 今天继续介绍Java面试常见的场景题:在线人数统计 现在用户…...
学习工具的一天之(burp)
第一呢一定是先下载 【Java环境】:Java Downloads | Oracle 下来是burp的下载 Download Burp Suite Community Edition - PortSwigger 【下载方法二】关注的一个博主 【BurpSuite 安装激活使用详细上手教程 web安全测试工具】https://www.bilibili.com/video/BV…...
归并排序:分治哲学的完美演绎与时空平衡的艺术
引言:跨越世纪的算法明珠 在计算机科学的璀璨星河中,归并排序犹如一颗恒久闪耀的明星。1945年,现代计算机之父冯诺伊曼在EDVAC计算机的研发过程中首次系统性地提出了这一算法,其精妙的分治思想不仅奠定了现代排序算法的理论基础&…...
蓝桥杯4T平台(串口打印电压值)
知识点:串口(单片机发送数据)按键ADC 题目 配置 代码 adc.c uint16_t getadc2(void) {uint16_t adc0;HAL_ADC_Start(&hadc2);adcHAL_ADC_GetValue(&hadc2);return adc; } adc.h uint16_t getadc2(void); main.c #include "lcd.h" #include…...
Stable Diffusion Prompt编写规范详解
Stable Diffusion Prompt编写规范详解 一、语法结构规范 (一)基础模板框架 [质量强化] [主体特征] [环境氛围] [风格控制] [镜头参数]质量强化:best quality, ultra detailed, 8k resolution主体特征:(1girl:1.3), long …...
es6常见知识点
官方文档:[https://es6.ruanyifeng.com/](https://es6.ruanyifeng.com/) 一、Class 1、Class Class只是一个语法糖,其功能用es5也能实现,但是比es5更符合类的期待 定义: constructor代表构造方法,而this指向new 生成的实例 定义类方法时,可以不使用function 注…...
leetcode1 两数之和 哈希表
什么时候使用哈希法,当我们需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法。 242. 有效的字母异位词 (opens new window)这道题目是用数组作为哈希表来解决哈希问题,349. 两个数组的交集 (o…...
Java中lombok的@Data注解【布尔类型】字段定义方式
文章目录 背景第一步、场景复现第二步、分析问题第三步、实现方案总结 背景 在Data注解的bean中添加Boolean字段时,set方法正常,get方法无法获取。 第一步、场景复现 在OrderInfo的实体中,新增布尔类型的字段:支付过【hasPaid】…...
理解数学概念——稠密性(density)
目录 1. 定义 2. 等价定义 3. 直观理解 1. 定义 在拓扑学(topology)和数学相关领域中,对于一个拓扑空间 X 的一个子集 A,若 X的每一个点要么属于A ,要么无限“接近”X的某个成员,则称这个子集 A 是稠密的(dense)或称A具有稠密性…...
【Spring AOP】_切点类的切点表达式
目录 1. 根据方法签名匹配编写切点表达式 1.1 具体语法 1.2 通配符表达规范 2. 根据注解匹配编写切点表达式 2.1 实现步骤 2.2 元注解及其常用取值含义 2.3 使用自定义注解 2.3.1 编写自定义注解MyAspect 2.3.2 编写切面类MyAspectDemo 2.3.3 编写测试类及测试方法 在…...
通过多线程获取RV1126的AAC码流
目录 一RV1126多线程获取音频编码AAC码流的流程 1.1AI模块的初始化并使能 1.2AENC模块的初始化 1.3绑定AI模块和AENC模块 1.4多线程获取每一帧AAC码流 1.5每个AAC码流添加ADTSHeader头部 1.6写入具体每一帧AAC的…...
HDFS 为什么不适合处理小文件?
目录 一、HDFS 是什么? 1. 核心目标 2. 基本架构 二、HDFS 为什么不适合处理小文件? 1. 元数据管理问题 2. 存储效率低下 3. 访问性能问题 4. 计算框架效率问题 5. 其他限制 一、HDFS 是什么? HDFS(Hadoop 分布式文件系统…...
网络空间安全(14)编辑器漏洞
一、概述 网页在线编辑器允许用户在网页上进行文本的编辑,并设置字体样式、段落行间距等,类似于使用Word进行编辑。然而,由于编辑器在处理用户输入、文件上传、权限控制等方面可能存在安全缺陷,因此容易成为攻击者利用的目标。 二…...
SpringMvc与Struts2
一、Spring MVC 1.1 概述 Spring MVC 是 Spring 框架的一部分,是一个基于 MVC 设计模式的轻量级 Web 框架。它提供了灵活的配置和强大的扩展能力,适合构建复杂的 Web 应用程序。 1.2 特点 轻量级:与 Spring 框架无缝集成,依赖…...
Avalonia 打包成deb
参考 https://www.cnblogs.com/Fengyinyong/p/13346642.html 安装工具 dotnet tool install --global dotnet-deb 还原包 dotnet restore -r linux-x64 dotnet deb install 打包,其中/p:SelfContainedtrue是独立运行 dotnet msbuild XXXCore.csproj /t:Creat…...
服务器数据恢复—raid5阵列中硬盘掉线导致上层应用不可用的数据恢复案例
服务器数据恢复环境&故障: 某公司一台服务器,服务器上有一组由8块硬盘组建的raid5磁盘阵列。 磁盘阵列中2块硬盘的指示灯显示异常,其他硬盘指示灯显示正常。上层应用不可用。 服务器数据恢复过程: 1、将服务器中所有硬盘编号…...
除了合并接口,还有哪些优化 Flask API 的方法?
除了合并接口,还有许多其他方法可以优化 Flask API,以下从性能优化、代码结构优化、安全性优化、错误处理优化等方面详细介绍: 性能优化 1. 使用缓存 内存缓存:可以使用 Flask-Caching 扩展来实现内存缓存,减少对数…...
制服小程序的“滑手”:禁用页面左右滑动全攻略
哈哈,看来你已经很聪明地发现了小程序中左右滑动的“顽皮”行为!😄 没错,我们可以通过设置 disableScroll 属性来“管教”它,同时结合 CSS 样式让页面既禁得住横向“乱跑”,又能顺畅地上下滚动。你的方案已…...
学习日记-250305
阅读论文:Leveraging Pedagogical Theories to Understand Student Learning Process with Graph-based Reasonable Knowledge Tracing ps:代码逻辑最后一点还没理顺,明天继续 4.2 Knowledge Memory & Knowledge Tracing 代码研究: 一般…...
DeepSeek R1模型医疗机构本地化部署评估分析(Discuss V1版上)
为了确保医疗机构在部署和应用DeepSeek R1模型时的成功,可以根据各个步骤设计一套综合的评估和评测体系。该体系将帮助医疗机构在实施过程中持续跟踪效果、识别潜在问题并进行优化调整。以下是对各步骤的详细评估和评测体系设计。 1. 确定模型需求 在医疗机构上线DeepSeek R…...
java 查找连个 集合的交集部分数据
利用了Java 8的Stream API,代码简洁且效率高 import java.util.stream.Collectors; import java.util.List; import java.util.HashSet; import java.util.Set;public class ListIntersection {public static List<Long> findIntersection(List<Long> …...
Hadoop管理页看不到任务的问题
这个yarn分配任务了但是为空 在$HADOOP_HOME/conf/mapred-site.xml 原来的配置文件基础之上添加: <property><name>mapreduce.framework.name</name><value>yarn</value></property> 重启之后就好了...
cmake、CMakeLists.txt、make、ninja
文章目录 一、概念0.cmake官网1.什么是cmake2.为什么使用cmake3.CMakeLists.txt 二、CMakeLists.txt语法:如何编写CMakeLists.txt,语法详解(0)语法基本原则(1)project关键字(2)set关键字(3)message关键字(4)add_executable关键字(5)add_subdirectory关键…...
PHP之Cookie和Session
在你有别的编程语言的基础下,你想学习PHP,可能要了解的一些关于cookie和session的信息。 Cookie 参数信息 setcookie(name,value,expire, path, domain); name : Cookie的名称。 value : Cookie的值。 expire : Cookie的过期时间,可以是一…...
学习记录-用例设计编写
黑马测试视频记录 目录 一、 软件测试流程 二、测试用例编写格式 1、等价类法 2、边界值分析法 3、 判定表法 4、场景法编辑 5、错误推荐法 一、 软件测试流程 二、测试用例编写格式 1、等价类法 2、边界值分析法 3、 判定表法 4、场景法 5、错误推荐法 时间紧任务重…...
【Docker】容器安全之非root用户运行
【Docker】容器安全之非root用户运行 1. 场景2. 原 Dockerfile 内容3. 整改结果4. 非 root 用户带来的潜在问题4.1 文件夹读写权限异常4.2 验证文件夹权限 1. 场景 最近有个项目要交付,第三方测试对项目源码扫描后发现一个问题,服务的 Dockerfile 都未指…...
CVE-2025-0392:JeeWMS graphReportController.do接口SQL注入漏洞复现
文章目录 CVE-2025-0392:JeeWMS graphReportController.do接口SQL注入漏洞复现0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.构造POC2.复现CVE-2025-0392:JeeWMS graphReportController.do接口SQL注入漏洞复现 0x01 前言 免责声明:请勿利用文章内的相…...
如何使用 Python+Flask+win32print 实现简易网络打印服务1
Python 实现网络打印机:Flask win32print 在工作场景中,我们可能需要一个简单的网页接口,供他人上传文档并自动打印到指定打印机。 本文将演示如何使用 Python Flask win32print 库来实现这一需求。 代码详见:https://github.…...
Ubuntu20.04双系统安装及软件安装(十一):向日葵远程软件
Ubuntu20.04双系统安装及软件安装(十一):向日葵远程软件 打开向日葵远程官网,下载图形版本: 在下载目录下打开终端,执行: sudo dpkg -i SunloginClient(按tab键自动补全)出现报错: …...
鸿蒙启动页开发
鸿蒙启动页开发 1.1 更改应用名称和图标 1.更改应用图标 找到moudle.json5文件,找到应用启动的EntryAbility下面的icon,将原来的图标改成自己设置的即可 2.更改应用名称 3.效果展示 2.1 广告页面开发 3.1 详细介绍 3.1.1 启动页面 import { PrivacyDialog } fr…...
认知动力学视角下的生命优化系统:多模态机器学习框架的哲学重构
认知动力学视角下的生命优化系统:多模态机器学习框架的哲学重构 一、信息熵与生命系统的耗散结构 在热力学第二定律框架下,生命系统可视为负熵流的耗散结构: d S d i S d e S dS d_iS d_eS dSdiSdeS 其中 d i S d_iS diS为内部熵…...
【Python编程】高性能Python Web服务部署架构解析
一、FastAPI 与 Uvicorn/Gunicorn 的协同 1. 开发环境:Uvicorn 直接驱动 作用:Uvicorn 作为 ASGI 服务器,原生支持 FastAPI 的异步特性,提供热重载(--reload)和高效异步请求处理。 启动命令: u…...
仿mudou库one thread oneloop式并发服务器
项目gitee:仿muduo: 仿muduo 一:项目目的 1.1项目简介 通过咱们实现的⾼并发服务器组件,可以简洁快速的完成⼀个⾼性能的服务器搭建。 并且,通过组件内提供的不同应⽤层协议⽀持,也可以快速完成⼀个⾼性能应⽤服务器…...
AI推理模型竞赛:从DeepSeek R1到Claude 3.7的关键进展
摘要 在Reasoning Model首轮竞赛中,从R1到Sonnet 3.7,AI领域取得了显著进展。DeepSeek R1的发布激发了推理模型的竞争。过去一个月内,顶尖AI实验室相继推出了三款最新的SOTA推理模型:OpenAI的o3-mini和deep research,x…...
AORO P9000 PRO三防平板携手RTK高精度定位,电力巡检效率倍增
电网系统覆盖幅员辽阔,每年因设备故障导致的巡检耗时超过百万工日。传统巡检模式受限于定位误差、设备防护不足和作业效率低下三大核心痛点,亟需智能化工具的突破性革新。为了满足这一需求,遨游通讯推出AORO P9000 PRO三防平板,以…...
【Linux———信号精讲】
你是怎么做到的,给了她想要的爱............................................................................................ 文章目录 前言 一、【信号入门】 1.1、【生活角度的信号】 1.2、【ctrl c与z】 1.3、【信号的发送与记录】 1.4、【信号处理常见方式…...
Unity 文字高度自适应
期望 文字有字号限制,输入文字文字后先判断高度是否适用于限制字号,若处于最小字号时高度任不适用,则调整RectTransform 的高度。 核心代码 每次输入文字时先将字号设定为原始字号。 comp.fontSize fontSize; comp.text content; 拓展T…...
鸿蒙通过用户首选项实现数据持久化
鸿蒙通过用户首选项实现数据持久化 1.1 场景介绍 用户首选项为应用提供Key-Value键值型的数据处理能力,支持应用持久化轻量级数据,并对其修改和查询。当用户希望有一个全局唯一存储的地方,可以采用用户首选项来进行存储。Preferences会将该…...
数字图像相关(DIC)技术用于生物力学和生物材料测试
生物医学工程是一个跨学科科学领域,旨在改善人类健康和医疗护理。从工程的角度来看,生物材料、力生物学和生物制造与目标生物系统的相互作用,以实现各种医学治疗目的。数字图像相关(DIC)技术,作为一种非接触、精准高效、无损的全场…...
java8中young gc的垃圾回收器选型,您了解嘛
在 Java 8 的 Young GC(新生代垃圾回收)场景中,对于 ToC的场景,即需要尽可能减少垃圾回收停顿时间以满足业务响应要求的场景,以下几种收集器各有特点,通常 Parnew和 G1 young表现较为出色,下面详…...
【五.LangChain技术与应用】【13.LangChain与智普大模型接入:行业领先的AI整合】
当LangChain遇到智普大模型:拆解一个AI整合的超级方案 最近半年,我一直在跟几个创业团队合作搞AI落地项目,发现一个特别有意思的现象:现在企业想用大模型干点实事,最大的痛点反而不是模型本身的能力,而是怎么把模型"塞"进现有系统里,还要塞得优雅、塞得高效。…...
【 <一> 炼丹初探:JavaWeb 的起源与基础】之 Servlet 与 JSP 的协作:MVC 模式的雏形
<前文回顾> 点击此处查看 合集 https://blog.csdn.net/foyodesigner/category_12907601.html?fromshareblogcolumn&sharetypeblogcolumn&sharerId12907601&sharereferPC&sharesourceFoyoDesigner&sharefromfrom_link <今日更新> 一、Servl…...