目录
一、SQLite
🍅 1、添加依赖
🍅 2、修改配置文件后缀(properties -> yaml)
🍅 3、编写配置文件
二、建立数据表
三、添加插入和删除方法
四、整合数据库操作(DataBaseManger类)
🍅 1、初始化方法init()
🍅 2、编写代码
五、对数据库操作进行单元测试
🍅 1、“准备工作”和“收尾工作”
🍅 2、编写测试类进行用力测试
🎈测试init()方法
🎈测试 交换机(插入和删除)
* Delete
🎈 测试DataBaseManager的队列(插入和删除)
🎈测试Binding
六、小结
🍅 1、运行时可能会报错
🍅 2、已经完成的任务
一、SQLite
MySQL数据库本身是比较重量的,所以这里使用SQLite,SQLite是更轻量的数据库
SQLite的优点:
- 服务器性能和内存要求低
- 减少了能源消耗
- 自成一体,便于携带
- 默认包含在所有的PHP安装中
它是一个本地的数据库, 操作该数据库就相当与直接操作本地的硬盘文件。
SQLite应用是很广泛的,在一些性能不高的设备上,比如移动端和嵌入式设备,就可以使用SQLite。
而且,也可以通过mybatis来使用。这里创建一个mapper文件夹将有关mybatis的xml文件放到其中。
🍅 1、添加依赖
使用过SQLite不用额外安装,直接引入依赖即可。
<!-- https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc -->
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.41.0.0</version>
</dependency>
引入以后,reload。
🍅 2、修改配置文件后缀(properties -> yaml)
这里的配置文件主要使用yaml格式。
🍅 3、编写配置文件
对于SQLite文件来说,不需要指定用户名和密码,原因如下:
首先,MySQL是一个客户端服务器结构的程序,一个数据库服务器,就会对应有很多个客户端来访问;
但是,SQLite不同,它不是一个客户端服务器结构的程序,只有自己一个人能够访问,把数据放在本地文件上面,只有当前主机才能访问,和网络无关。
spring:
datasource:
# SQLite数据库是将数据存储在当前硬盘的某个指定的为文件中
# 这是一个相对路径,运行以后,这个文件就会出现在当前项目的目录中
url: jdbc:sqlite:./data/meta.db
# SQLite并不需要指定用户名和密码
username:
password:
driver-class-name: org.sqlite.JDBC
mybatis:
mapper-location: classpath:mapper/**Mapper
二、建立数据表
SQLite没有建数据库的这概念,一个.db文件就相当于一个库,程序一启动就会自动建库。
主要建立以下几个表:
* 交换机存储
* 队列存储
* 绑定存储
这里通过代码自动完成建表操作,使用Mybatis执行SQL语句。
MyBatis基本使用流程回顾:
(1)创建一个interface,描述有哪些方法要给java代码使用
(2)创建对应的xml,通过xml实现interface中的抽象方法
创建一个mapper包,放置interface。
在MeteMapper接口中建立三个核心的建表方法:
@Mapper
public interface MetaMapper {
//提供三个核心建表方法
void createExchangeTable();
void createQueueTable();
void createBindingTable();
}
然后再Mapper文件夹中建立对应的xml文件,
编写myBatis文件,关于myBatis框架的使用,如果不了解,可以参考博客https://blog.csdn.net/qq_52136076/category_12392841.html?spm=1001.2014.3001.5482
<?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.tigermq.mqserver.mapper.MetaMapper">
<update id="createExchangeTable">
create table if not exists exchange(
name varchar(50) primary key,
type int,
durable boolean,
)
</update>
<update id="createQueueTable">
create table if not exists queue(
name varchar(50) primary key,
durable boolean,
exclusive boolean,
)
</update>
<update id="createBindingTable">
create table if not exists binding(
exchangeName varchar(50),
queueName varchar(50),
bindingKey varchar(256)
)
</update>
</mapper>
三、添加插入和删除方法
在MentaMapper接口中添加插入和删出方法,其中
对于交换机和队列这两个表,由于使用name作为主键,所以直接按照name进行删除即可。
而对于绑定来说,没有主键,删除操作其实是针对exchangeName和queueName两个维度进行筛选。
// 针对三个表,进行插入\删除\查找操作
@Insert("insert into exchange values(#{name},#{type},#{durable})")
void insertExchange(Exchange exchange);
@Select("select * from exchange")
List<Exchange> selectAllExchanges();
@Delete("delete from exchange where name = #{exchangeName}")
void deleteExchange(String exchangeName);
@Insert("insert into queue values (#{name},#{durable},#{exclusive})")
void insertQueue(MSGQueue queue);
@Select("select * from queue")
List<MSGQueue> selectAllQueues();
@Delete("delete from queue where name = (#{queueName})")
void deleteQueue(String queueName);
@Insert("insert into binding values(#{exchangeName},#{queueName},#{bindingKey})")
void insertBinding(Binding binding);
@Select("select * from binding")
List<Binding> selectAllBindings();
@Delete("delete from binding where exchangeName = #{exchangeName} and queueName = #{queueName}")
void deleteBinding(Binding binding);
四、整合数据库操作(DataBaseManger类)
创建一个包datacenter .都是针对数据进行操作
在包中创建一个DataBaseManger类
🍅 1、初始化方法init()
首先,由于构造方法初始化一般都不会带有很多逻辑(也可以用,但是一般习惯不适用构造方法),而这里初始化数据库需要很多业务逻辑,所以就自定义了一个init()方法。
数据库的初始化其实就是 建库建表 + 插入一些默认数据
基本逻辑:
如果数据已经存在了,就不做出任何操作
如果数据不存在,则创 建库 + 表 + 构造默认数据
如何判断数据库是否存在?
答:判断meta.db文件是否存在。
🍅 2、编写代码
根据前面的逻辑,编写如下代码:
/*
* 通过这个类,整合之前的数据库操作
* */
public class DataBaseManger {
// 从spring中拿到现成的对象
private MetaMapper metaMapper;
// 针对数据库进行初始化
// 因为这里的的初始化需要带有业务逻辑,所以就不适用构造方法,因为构造方法一般不会涉及到很多的业务逻辑
public void init(){
// 手动的获取到MetaMapper
metaMapper = TigerMqApplication.context.getBean(MetaMapper.class);
if(!checkDBExists()){
// 创建一个data目录
File dataDir = new File("/data");
dataDir.mkdirs();
// 数据库不存在,就进行建库建表操作
createTable();
// 创建默认数据
createDafaultData();
System.out.println("[DataBaseManger]数据库初始化完成");
}else{
// 数据库存在了
System.out.println("[DataBaseManger]数据库已经存在");
}
}
public void deleteDB(){
File file = new File("./data/meta.db");
boolean ret = file.delete();
if (ret){
System.out.println("[DataBaseManager]DB文件已经删除成功");
}else{
System.out.println("[DataBaseManager]DB文件删除失败");
}
//这个Delete只能删除空目录,所以删除的时候要保证目录是空的
File dataDir = new File("./data");
ret = dataDir.delete();
if(ret){
System.out.println("[DataBaseManger] 删除数据库目录成功");
}else {
System.out.println("[DataBaseManger] 删除数据库目录失败");
}
}
// 判断文件是否存在
private boolean checkDBExists() {
File file = new File("./data/meta.db");
if (file.exists()){
return true;
}
return false;
}
// 建表
// 建库操作不需要手动执行
// 首次执行,会自动创建出meta.db文件(mybatis会帮助我们完成)
private void createTable() {
// 下面这些方法之前已经创建过了
metaMapper.createExchangeTable();
metaMapper.createQueueTable();
metaMapper.createBindingTable();
System.out.println("[DataBaseManger]创建表完成");
}
// 创建默认数据
// 此处主要是添加一个默认的交换机:DIRECT
private void createDafaultData() {
// 构造一个默认的交换机
Exchange exchange = new Exchange();
exchange.setName("");
exchange.setType(ExchangeType.DIRECT);
exchange.setDurable(true);
exchange.setAutoDelete(false);
metaMapper.insertExchange(exchange);
System.out.println("[DataBaseManger]创建初始数据已经完成");
}
// 其他的一些数据库操作
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 queue){
metaMapper.insertQueue(queue);
}
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还没有进行构造,为空,直接运行就会报错,但是这里不能使用@Autowired进行对象注入,因为里不打算把DataBaseManager设置为一个Bean,所以我们就,我们可以在启动类中,对对象metaMapper进行构造。
在启动类中:
@SpringBootApplication
public class TigerMqApplication {
public static ConfigurableApplicationContext context;
public static void main(String[] args) {
context = SpringApplication.run(TigerMqApplication.class, args);
}
}
在init()方法中手动获取到metaMapper对象:添加以下代码
// 手动的获取到MetaMapper
metaMapper = TigerMqApplication.context.getBean(MetaMapper.class);
五、对数据库操作进行单元测试
创建测试类DataBaseManagerTests
🍅 1、“准备工作”和“收尾工作”
添加两个类,主要是为了放置每个测试用力之间不会收到干扰而创建的。
首先是“准备工作”setUp(),主要是为了调用init()方法,初始化数据库
然后是“收尾工作”tearDown(),主要是为了删除掉.db文件。
@BeforEach指的是每个用例执行前都会调用这个方法
@AfterEach指的是每个用力执行完后调用这个方法
编写代码:
@SpringBootTest
public class DataBaseMangerTests {
private DataBaseManger dataBaseManger = new DataBaseManger();
// 编写多个方法,每个方法都是一组单元测试用例
// 编写两个方法,分别用于进行“准备工作”和收尾工作
// 这是为了让每个测试用力之间不会收到干扰而创建的
//使用该方法,进行准备工作,每个用力执行前都要调用这个方法
@BeforeEach
public void setUp(){
// 由于init中,需要经过context对象拿到metaMapper示例
// 所以需要先把context对象构造出来
TigerMqApplication.context = SpringApplication.run(TigerMqApplication.class);
dataBaseManger.init();
}
// 该方法用来执行收尾工作,每个用例执行后,需要调用这个方法
@AfterEach
public void tearDown(){
// 及那个数据库清空,删掉.db文件
// 删除之前需要关闭context对象。
// 原因是因为context持有了MetaMapper类的实力对象,
// 而该对象打开了meta.db文件,而在打开的情况下,删除操作是不能进行的
TigerMqApplication.context.close();
dataBaseManger.deleteDB();
}
}
🍅 2、编写测试类进行用力测试
🎈测试init()方法
@Test
public void testInitTable(){
// 由于init()方法已经被调用过了,直接在测试用力代码中检查当前数据库状态
// 从数据库中查询数据是否符合预期
// 查交换机表,会有一个匿名exchange数据
List<Exchange> exchangeList = dataBaseManger.selectAllExchanges();
List<MSGQueue> queueList = dataBaseManger.selectAllQueues();
List<Binding> bindingList = dataBaseManger.selectAllBindings();
// 使用断言
// 判断1和exchangeList是否相等
// assertEquals(预期值,实际值)
Assertions.assertEquals(1,exchangeList.size());
Assertions.assertEquals("",exchangeList.get(0).getName());
Assertions.assertEquals(ExchangeType.DIRECT,exchangeList.get(0).getType());
Assertions.assertEquals(0,queueList.size());
Assertions.assertEquals(0,bindingList.size());
}
🎈测试 交换机(插入和删除)
* Insert
private Exchange createTestExchange(String exchangeName){
Exchange exchange = new Exchange();
exchange.setName(exchangeName);
exchange.setType(ExchangeType.FANOUT);
exchange.setDurable(true);
return exchange;
}
@Test
public void testInsertExchange(){
// 构造一个Exchange对象,插入到数据库中,再查询出来,看是否符合预期
Exchange exchange = createTestExchange("testExchange");
dataBaseManger.insertExchange(exchange);
List<Exchange> exchangeList = dataBaseManger.selectAllExchanges();
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());
}
* Delete
@Test
public void testDeleteExchange(){
// 先构造一个交换机
Exchange exchange = createTestExchange("testExchange");
dataBaseManger.insertExchange(exchange);
List<Exchange> exchangeList = dataBaseManger.selectAllExchanges();
Assertions.assertEquals(2,exchangeList.size());
Assertions.assertEquals("testExchange",exchangeList.get(1).getName());
// 进行删除操作
dataBaseManger.deleteExchange("testExchange");
// 再次查询
exchangeList = dataBaseManger.selectAllExchanges();
Assertions.assertEquals(1,exchangeList.size());
Assertions.assertEquals("",exchangeList.get(0).getName());
}
🎈 测试DataBaseManager的队列(插入和删除)
* Insert
private MSGQueue createTestQueue(String queueName){
MSGQueue queue = new MSGQueue();
queue.setName(queueName);
queue.setDurable(true);
queue.setExclusive(false);
return queue;
}
@Test
public void testInsertQueue(){
MSGQueue queue = createTestQueue("testQueue");
dataBaseManger.insertQueue(queue);
List<MSGQueue> queueList = dataBaseManger.selectAllQueues();
Assertions.assertEquals(1,queueList.size());
MSGQueue newQueue = queueList.get(0);
Assertions.assertEquals("testQueue",newQueue.getName());
}
* Delete
@Test
public void testDeleteQueue(){
MSGQueue queue = createTestQueue("testQueue");
dataBaseManger.insertQueue(queue);
List<MSGQueue> queueList = dataBaseManger.selectAllQueues();
Assertions.assertEquals(1,queueList.size());
// 删除
dataBaseManger.deleteQueue("testQueue");
queueList = dataBaseManger.selectAllQueues();
Assertions.assertEquals(0,queueList.size());
}
🎈测试Binding
* Insert
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","tesQueue");
dataBaseManger.insertBinding(binding);
List<Binding> bindingList = dataBaseManger.selectAllBindings();
Assertions.assertEquals(1,bindingList.size());
Assertions.assertEquals("testExchange",bindingList.get(0).getExchangeName());
Assertions.assertEquals("tesQueue",bindingList.get(0).getQueueName());
Assertions.assertEquals("testBindingKey",bindingList.get(0).getBindingKey());
}
*Delete
@Test
public void testDeleteBinding(){
Binding binding = createTestBinding("testExchange","testQueue");
dataBaseManger.insertBinding(binding);
List<Binding> bindingList = dataBaseManger.selectAllBindings();
Assertions.assertEquals(1,bindingList.size());
// 删除操作
Binding toDeleteBinding = createTestBinding("testExchange","testQueue");
dataBaseManger.deleteBinding(toDeleteBinding);
bindingList = dataBaseManger.selectAllBindings();
Assertions.assertEquals(0,bindingList.size());
}
六、小结
🍅 1、运行时可能会报错
在刚开始测试运行的过程中,代码可能会报错。我在运行的时候就遇到了以下的错误,你们可能也会遇见
解决方案我是上网查到的:
Error creating bean with name ‘dataSource‘ defined in class path resource解决办法_张道长的博客-CSDN博客
🍅 2、已经完成的任务
(1)项目需求分析:项目实战 — 消息队列(1) {需求分析}_️藿香正气水的博客-CSDN博客
(2)设计核心类: 项目实战 — 消息队列(2){创建核心类}_️藿香正气水的博客-CSDN博客
(3)设计数据库,并且针对数据库代码进行了单元测试