【消息隊列】數據庫的數據管理

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"));}// ......

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/71424.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/71424.shtml
英文地址,請注明出處:http://en.pswp.cn/web/71424.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

飛機大戰lua迷你世界腳本

-- 迷你世界飛機大戰 v1.2 -- 星空露珠工作室制作 -- 最后更新&#xff1a;2024年1月 ----------------------------- -- 迷你世界API適配配置 ----------------------------- local UI { BASE_ID 7477478487091949474-22856, -- UI界面ID ELEMENTS { BG 1, -- 背景 BTN_LE…

圖解MOE大模型的7個核心問題并探討DeepSeekMoE的專家機制創新

原文地址:https://newsletter.maartengrootendorst.com/p/a-visual-guide-to-mixture-of-experts #mermaid-svg-FU7YUSIfuXO6EVHa {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-FU7YUSIfuXO6EVHa .error-icon{fill…

【智能機器人開發全流程:硬件選型、軟件架構與ROS實戰,打造高效機器人系統】

文章目錄 1. 硬件層設計(1) 傳感器選型(2) 計算平臺 2. 軟件架構設計(1) 核心模塊劃分(2) 通信框架 3. 關鍵實現步驟(1) 硬件-軟件接口開發(2) SLAM與導航實現(3) 仿真與測試 4. 典型框架示例基于ROS的移動機器人分層架構 5. 優化與擴展6. 開源項目參考 1. 硬件層設計 (1) 傳感…

React Native v0.78 更新

這個版本在 React Native 中引入了 React 19&#xff0c;并帶來了一些重要的新功能&#xff0c;例如 Android 矢量圖&#xff08;Vector Drawables&#xff09;的原生支持 以及 iOS 更好的 Brownfield 集成。 亮點 React 19 集成更小更快的發布節奏可選擇在 Metro 中啟用 Jav…

機器學習數學通關指南

? 寫在前面 &#x1f4a1; 在代碼的世界里沉浸了十余載&#xff0c;我一直自詡邏輯思維敏捷&#xff0c;編程能力不俗。然而&#xff0c;當我初次接觸 DeepSeek-R1 并領略其清晰、系統的思考過程時&#xff0c;我不禁為之震撼。那一刻&#xff0c;我深刻意識到&#xff1a;在A…

MySQL 實驗1:Windows 環境下 MySQL5.5 安裝與配置

MySQL 實驗1&#xff1a;Windows 環境下 MySQL5.5 安裝與配置 目錄 MySQL 實驗1&#xff1a;Windows 環境下 MySQL5.5 安裝與配置 一、MySQL 軟件的下載二、安裝 MySQL三、配置 MySQL 1、配置環境變量2、安裝并啟動 MySQL 服務3、設置 MySQL 字符集4、為 root 用戶設置登錄密…

煙花燃放安全管控:智能分析網關V4煙火檢測技術保障安全

一、方案背景 在中國諸多傳統節日的繽紛畫卷中&#xff0c;煙花盛放、燒紙祭祀承載著人們的深厚情感。一方面&#xff0c;煙花璀璨&#xff0c;是對節日歡慶氛圍的熱烈烘托&#xff0c;寄托著大家對美好生活的向往與期許&#xff1b;另一方面&#xff0c;裊裊青煙、點點燭光&a…

Elasticsearch:解鎖深度匹配,運用Elasticsearch DSL構建閃電般的高效模糊搜索體驗

目錄 Elasticsearch查詢分類 葉子查詢 全文檢索查詢 match查詢 multi_match查詢 精確查詢 term查詢 range查詢 復雜查詢 bool查詢簡單應用 bool查詢實現排序和分頁 bool查詢實現高亮 場景分析 問題思考 解決方案 search_after方案(推薦) point in time方案 方案…

數據安全VS創作自由:ChatGPT與國產AI工具隱私管理對比——論文黨程序員必看的避坑指南

文章目錄 數據安全VS創作自由&#xff1a;ChatGPT與國產AI工具隱私管理對比——論文黨程序員必看的避坑指南ChatGPTKimi騰訊元寶DeepSeek 數據安全VS創作自由&#xff1a;ChatGPT與國產AI工具隱私管理對比——論文黨程序員必看的避坑指南 產品隱私設置操作路徑隱私協議ChatGPT…

FFmpeg入門:最簡單的音視頻播放器

FFmpeg入門&#xff1a;最簡單的音視頻播放器 前兩章&#xff0c;我們已經了解了分別如何構建一個簡單和音頻播放器和視頻播放器。 FFmpeg入門&#xff1a;最簡單的音頻播放器 FFmpeg入門&#xff1a;最簡單的視頻播放器 本章我們將結合上述兩章的知識&#xff0c;看看如何融…

windows 上刪除 node_modules

在 Windows 11 上&#xff0c;你可以通過命令行來刪除 node_modules 文件夾并清除 npm 緩存。以下是具體步驟&#xff1a; 刪除 node_modules 打開命令提示符&#xff08;Command Prompt&#xff09;或終端&#xff08;PowerShell&#xff09;。 導航到項目目錄。你可以使用 …

kotlin的val聲明的變量是常量嗎

kotlin的val聲明的變量是常量嗎 DeepSeek R1 思考 65 秒 思考過程 嗯&#xff0c;用戶問的是Kotlin中的val聲明的變量是不是常量。首先我需要回憶一下Kotlin的基礎知識。記得在Kotlin里&#xff0c;val用來聲明只讀變量&#xff0c;也就是說一旦賦值之后就不能再重新賦值了。…

Linux下學【MySQL】中如何實現:多表查詢(配sql+實操圖+案例鞏固 通俗易懂版~)

每日激勵&#xff1a;“不設限和自我肯定的心態&#xff1a;I can do all things。 — Stephen Curry” 緒論?&#xff1a; 本章是MySQL篇中&#xff0c;非常實用性的篇章&#xff0c;相信在實際工作中對于表的查詢&#xff0c;很多時候會涉及多表的查詢&#xff0c;在多表查詢…

【基礎4】插入排序

核心思想 插入排序是一種基于元素比較的原地排序算法&#xff0c;其核心思想是將數組分為“已排序”和“未排序”兩部分&#xff0c;逐個將未排序元素插入到已排序部分的正確位置。 例如撲克牌在理牌的時候&#xff0c;一般會將大小王、2、A、花牌等按大小順序插入到左邊&…

【Flink銀行反欺詐系統設計方案】3.欺詐的7種場景和架構方案、核心表設計

【Flink銀行反欺詐系統設計方案】3.欺詐的7種場景和架構方案、核心表設計 1. **欺詐場景分類與案例說明**1.1 **大額交易欺詐**1.2 **異地交易欺詐**1.3 **高頻交易欺詐**1.4 **異常時間交易欺詐**1.5 **賬戶行為異常**1.6 **設備指紋異常**1.7 **交易金額突變** 2. **普適性軟…

迷你世界腳本生物接口:Creature

生物接口&#xff1a;Creature 彼得兔 更新時間: 2024-05-22 17:51:22 繼承自 Actor 具體函數名及描述如下: 序號 函數名 函數描述 1 getAttr(...) 生物屬性獲取 2 setAttr(...) 生物屬性設置 3 isAdult(...) 判斷該生物是否成年 4 setOxygenNeed(…

深入理解三色標記、CMS、G1垃圾回收器

三色標記算法 簡介 三色標記算法是一種常見的垃圾收集的標記算法&#xff0c;屬于根可達算法的一個分支&#xff0c;垃圾收集器CMS&#xff0c;G1在標記垃圾過程中就使用該算法 三色標記法&#xff08;Tri-color Marking&#xff09;是垃圾回收中用于并發標記存活對象的核心算…

自動駕駛---不依賴地圖的大模型軌跡預測

1 前言 早期傳統自動駕駛方案通常依賴高精地圖&#xff08;HD Map&#xff09;提供道路結構、車道線、交通規則等信息&#xff0c;可參考博客《自動駕駛---方案從有圖邁進無圖》&#xff0c;本質上還是存在問題&#xff1a; 數據依賴性高&#xff1a;地圖構建成本昂貴&#xf…

Xshell及Xftp v8.0安裝與使用-生信工具050

官網 https://www.xshell.com/zh/free-for-home-school/ XShell & Xftp 詳解 1. XShell 介紹 1.1 XShell 是什么&#xff1f; XShell 是一款強大的 Windows 終端模擬器&#xff0c;主要用于遠程管理 Linux、Unix 服務器。它支持 SSH、Telnet、Rlogin 及 SFTP 協議&…

跨域-告別CORS煩惱

跨域-告別CORS煩惱 文章目錄 跨域-告別CORS煩惱[toc]1-參考網址2-思路整理1-核心問題2-個人思考3-腦洞打開4-個人思考-修正版1-個人思考2-腦洞打開 3-知識整理1-什么是跨域一、同源策略簡介什么是源什么是同源是否是同源的判斷哪些操作不受同源策略限制跨域如何跨域 二、CORS 簡…