考虑数据库的使用:
本次项目采用的是相比于 MySQL更加轻量的数据库 SQLite,SQLite的是用也是非常的广泛,在一些性能不高的产品中的使用频率是非常高的,嵌入式设备,比如 Andioid系统,就是内置的 SQLite
SQLite 的轻量化体现在,一个完整的 SQLite 数据库,只有一个单独的可执行的文件不到1M,而且SQLite是一个本地的数据库,这个数据库直接操作的是本地的硬盘文件
在 Java 要中要想使用 SQLite 不用额外安装,引入相关的依赖即可,对于SQLite来说,在进行配置相关内容的时候是不需要指定用户名和密码的,它的数据是存放在本地的硬盘文件中的,和网络无关,SQLite虽然与MySQL存在不同,但是二者都是可以通过 MyBatis 这样的框架来使用的
建库(数据库文件,就是数据库),建表 => 当上述配置和依赖都准备好了之后,程序启动就会自动的建库
建表设计:核心数据库表有这几个:交换机存储,队列存储,绑定存储
针对于这几个核心类,很容易把表设计出来,但是具体是什么时机来执行上述操作? 按照以往的操作是先把数据库表直接创建好,在启动服务器,这些操作都是在部署阶段完成的,之前的操作部署一次即可,不会反复的进行操作,但是后续接触到的更多的程序可能会反复部署多次,能够简化部署的步骤,也是很关键的,在这个项目中,我们通过代码的方式来完成自动的建表操作
java
<?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.zcfe.mq.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.zcfe.mq.mqserver.core.Exchange">
insert into exchange values(#{name}, #{type}, #{durable}, #{autoDelete}, #{arguments});
</insert>
<select id="selectAllExchange" resultType="com.zcfe.mq.mqserver.core.Exchange">
select * from exchange;
</select>
<delete id="deleteExchange" parameterType="java.lang.String">
delete from exchange where name = #{exchangeName};
</delete>
<insert id="insertQueue" parameterType="com.zcfe.mq.mqserver.core.MSGQueue">
insert into queue values(#{name}, #{durable}, #{exclusive}, #{autoDelete}, #{arguments});
</insert>
<select id="selectAllQueues" resultType="com.zcfe.mq.mqserver.core.MSGQueue">
select * from queue;
</select>
<delete id="deleteQueue" parameterType="java.lang.String">
delete from queue where name = #{queueName};
</delete>
<insert id="insertBinding" parameterType="com.zcfe.mq.mqserver.core.Binding">
insert into binding values(#{exchangeName}, #{queueName}, #{bindingKey});
</insert>
<select id="selectAllBinding" resultType="com.zcfe.mq.mqserver.core.Binding">
select * from binding;
</select>
<delete id="deleteBinding" parameterType="com.zcfe.mq.mqserver.core.Binding">
delete from binding where exchangeName = #{exchangeName} and queueName = #{queueName};
</delete>
</mapper>
1.当前,是把每一个建表的语句,都单独列为一个 update 标签,并且对应一个 java 方法,能否改成,一个 update 标签中包含多个建表语句,同时借助一个 java 方法,完成上述多个表的创建呢?
其实在 MyBstis 中是支持这样的操作,一个标签中包含多个 sql 语句的(前提是MySQL 或者 Oracle) 对于 SQLite 无法做到上述的功能,当你一个 update 标签中,写了多个 create table 语句的时候,只有第一个语句能执行
java
private Map<String, Object> arguments = new HashMap<>();
public String getArguments() {
//是把当前的 arguments 参数,从 Map 转成 String (json)
ObjectMapper objectMapper = new ObjectMapper();
try {
return objectMapper.writeValueAsString(arguments);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
//如果代码真的出现了异常, 就返回一个空的 json 字符串
return "{}";
}
//这个方法,是从数据库读取数据之后,构造的 Exchange 对象,会自动的调用到
public void setArguments(String argumentsJson) {
ObjectMapper objectMapper = new ObjectMapper();
//其中的第二个参数,用来描述当前 json 字符串,要转成的 java 对象是啥类型的
//如果是一个简单的类型,直接使用对应的类型的类对象即可
//如果是集合类这样的复杂类型,可以使用 TypeReference 匿名内部类对象
//来描述复杂类型的具体信息(通过泛型来描述的)
try {
this.arguments = objectMapper.readValue(argumentsJson, new TypeReference<HashMap<String, Object>>() {});
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
如何实现 arguments 这件键值对,和数据库中的字符串类型相互转化呢?
关键的要点在于 MyBaits 在完成数据库操作的时候,会自动的调用对象的 getter 和 setter 操作
1.比如 MyBatis 往数据库中写数据的时候,就会调用对象的 getter 方法,拿到属性的值,在往数据库中写,如果这个过程中,让 getArguments 得到的结果是 String 类型的,此时就可以直接把这个数据写进数据库中
2.比如 MyBatis 从数据库中读数据的时候,就会调用对象的 setter 方法, 把数据库中读到的结果设置到对象的属性中,比如这个过程中,让 setArguments 参数是一个 String,并且在 setArguments 内部针对字符串进行解析,解析成一个 Map 对象
java
try {
this.arguments = objectMapper.readValue(argumentsJson, new TypeReference<HashMap<String, Object>>() {});
} catch (JsonProcessingException e) {
e.printStackTrace();
}
第二个参数"HashMap<String, Object>()" 用来描述=当前 json 字符串,要转成的java对象是啥类型的,如果是一个简单类型,就可以直接使用对应的类型类对象即可,如果是一个集合类这样的复杂类型,可以使用 TypeReference 匿名内部类对象的方式,来描述复杂类型的具体信息(通过泛型参数来描述的)
写一个类,整合上述的数据库操作
一般谈到初始化,会用到 构造方法,但是我们此处是一个单独的普通方法
构造方法,一般是用来初始化类的属性,一般不太会涉及到太多的业务逻辑,此处的初始化,带有业务逻辑,还是单独拎出来,手动调用比较合适一些
我们期望的是,在咱们得BrokerServer启动的时候,做出一下逻辑判定:
1.如果数据库已经存在了(表啥的都有了),不做任何操作
2.如果数据库不存在则建库建表,构造默认数据
java
//针对数据库进行初始化
public void init() {
//手动获取到 MetaMapper(防止空指针异常的情况出现)
metaMapper = MqApplication.context.getBean(MetaMapper.class);
if (!checkDBExists()) {
//数据不存在,就进行建库建表操作
//先创建一个 data 目录
File dataDir = new File("./data");
dataDir.mkdirs();
//创建表
createTable();
//插入数据
createDefaultData();
System.out.println("[DataBaseManager]数据库初始化完成");
} else {
//数据库存在,啥都不用干,
System.out.println("[DataBaseManager]数据库初始化完成");
}
1,啥叫存在? => 就是指 判定 meta.db 这个文件是否存在即可
针对 DataBaseManage 进行单元测试
设计测试用例的时候,要求单元测试用例和用例之间,是需要相互独立的,不会干扰的
java
//这个方法,来执行准备工作,每个用例执行前,都要调用这个方法
@BeforeEach
public void setUp() {
MqApplication.context = SpringApplication.run(MqApplication.class);
dataBaseManager.init();
}
//这个方法,来处理收尾工作,每个用例执行后,都要调用这个方法
@AfterEach
public void tearDown() {
//这里要进行的操作就是把数据库中的数据删除(也就是把数据库文件, meta.db 直接删除了就行)
//注意, 这里不能直接就删除,要先关闭上述的 context 对象
//此处的 context 对象持有了 MetaMapper 的实例,而 MetaMapper 由打开了 meta.db 数据库文件
//如果 meta.db 被别人打开了,此时删除数据库文件(meta.db)是会失败的,(这是受限于window的操作系统)
//另一方面,获取context会占用8080窗口,此处释放也是为了后面能够继续使用这个窗口
MqApplication.context.close();
dataBaseManager.deleteDB();
}
java
@Test
public void testInitTable() {
//由于 init 方法,已经在前面 setUp 调用过了, 直接在测试用例的代码中检查,数据库的状态即可
//直接从数据库中进行查询查看是否符合预期
//查交换机里面应该有一个数据(匿名的 Exchange), 查队列,没有数据,查绑定也没有数据
List<Exchange> exchangesList = dataBaseManager.selectAllExchange();
List<MSGQueue> queueList = dataBaseManager.selectAllQueue();
List<Binding> bindingList = dataBaseManager.selectAllBinding();
//这里通过断言来判定结果
//注意这里的两个参数的顺序,比较相等,谁在前谁在后,是没关系的
//这里 assertEquals 的形参,第一个形参是 expected(预期的),第二个形参是 actual(实际的)
Assertions.assertEquals(1, exchangesList.size());
Assertions.assertEquals("", exchangesList.get(0).getName());
Assertions.assertEquals(ExchangeType.DIRECT, exchangesList.get(0).getType());
Assertions.assertEquals(0, queueList.size());
Assertions.assertEquals(0, bindingList.size());
}
java
public Exchange createTestExchange(String exchangeName) {
Exchange exchange = new Exchange();
exchange.setName(exchangeName);
exchange.setType(ExchangeType.FANOUT);
exchange.setDurable(true);
exchange.setAutoDelete(false);
exchange.setArguments("aaa", 111);
exchange.setArguments("bbb", 222);
return exchange;
}
@Test
public void testInsertExchange(){
//构造一个 Exchange 对象,插入到数据库中,在查询出来,看是否符合预期
Exchange exchange = createTestExchange("testExchange");
dataBaseManager.insertExchange(exchange);
//插入完毕,查询结果
List<Exchange> exchangeList = dataBaseManager.selectAllExchange();
Assertions.assertEquals(2, exchangeList.size());
//这里获取第二个元素,就是刚刚插入的元素
Exchange newExchange = exchangeList.get(1);
Assertions.assertEquals("testExchange", newExchange.getName());
Assertions.assertEquals(ExchangeType.FANOUT, newExchange.getType());
Assertions.assertEquals(true, newExchange.isDurable());
Assertions.assertEquals(false, newExchange.isAutoDelete());
Assertions.assertEquals(111, newExchange.getArguments("aaa"));
Assertions.assertEquals(222, newExchange.getArguments("bbb"));
}
@Test
public void testDeleteExchange() {
//先创建一个交换机,插入数据,然后在按照名字删除即可
Exchange exchange = createTestExchange("testExchange");
dataBaseManager.insertExchange(exchange);
List<Exchange> exchangeList = dataBaseManager.selectAllExchange();
Assertions.assertEquals(2, exchangeList.size());
Assertions.assertEquals("testExchange", exchangeList.get(1).getName());
//进行删除操作
dataBaseManager.deleteExchange("testExchange");
Assertions.assertEquals(1, dataBaseManager.selectAllExchange().size());
//只剩下一个默认交换机
Assertions.assertEquals("", exchangeList.get(0).getName());
}
java
public MSGQueue createTestQueue(String queueName) {
MSGQueue queue = new MSGQueue();
queue.setName(queueName);
queue.setDurable(true);
queue.setAutoDelete(false);
queue.setExclusive(false);
queue.setArguments("aaa", 111);
queue.setArguments("bbb", 222);
return queue;
}
@Test
public void testInsertQueue() {
MSGQueue queue = createTestQueue("testQueue");
dataBaseManager.insertQueue(queue);
List<MSGQueue> queueList = dataBaseManager.selectAllQueue();
Assertions.assertEquals(1, queueList.size());
Assertions.assertEquals("testQueue", queueList.get(0).getName());
Assertions.assertEquals(true, queueList.get(0).isDurable());
Assertions.assertEquals(false, queueList.get(0).isExclusive());
Assertions.assertEquals(false, queueList.get(0).isAutoDelete());
Assertions.assertEquals(111, queueList.get(0).getArguments("aaa"));
Assertions.assertEquals(222, queueList.get(0).getArguments("bbb"));
}
@Test
public void testDeleteQueue() {
MSGQueue queue = createTestQueue("testQueue");
dataBaseManager.insertQueue(queue);
List<MSGQueue> queueList = dataBaseManager.selectAllQueue();
Assertions.assertEquals(1, queueList.size());
Assertions.assertEquals("testQueue", queueList.get(0).getName());
//进行删除操作
dataBaseManager.deleteQueue("testQueue");
Assertions.assertEquals(0, dataBaseManager.selectAllQueue().size());
}
java
public Binding createTestBinding(String exchangeName, String queueName) {
Binding binding = new Binding();
binding.setExchangeName(exchangeName);
binding.setQueueName(queueName);
binding.setBindingKey("testBindingKey");
return binding;
}
@Test
public void testInsertBinding() {
Binding binding = createTestBinding("testExchange", "testQueue");
dataBaseManager.insertBinding(binding);
List<Binding> bindingList = dataBaseManager.selectAllBinding();
Assertions.assertEquals(1, bindingList.size());
Assertions.assertEquals("testExchange", bindingList.get(0).getExchangeName());
Assertions.assertEquals("testQueue", bindingList.get(0).getQueueName());
Assertions.assertEquals("testBindingKey", bindingList.get(0).getBindingKey());
}
@Test
public void testDeleteBinding() {
Binding binding = createTestBinding("testExchange", "testQueue");
dataBaseManager.insertBinding(binding);
List<Binding> bindingList = dataBaseManager.selectAllBinding();
Assertions.assertEquals(1, bindingList.size());
Assertions.assertEquals("testExchange", bindingList.get(0).getExchangeName());
Assertions.assertEquals("testQueue", bindingList.get(0).getQueueName());
Assertions.assertEquals("testBindingKey", bindingList.get(0).getBindingKey());
//进行删除操作
dataBaseManager.deleteBinding(binding);
Assertions.assertEquals(0, dataBaseManager.selectAllBinding().size());
}