上一篇文章中我把一些关键的类以及表示出来,如何对这些类对应的对象进行管理呢?管理分为硬盘和内存上,硬盘又分为数据库(管理交换机,队列和绑定)和文件(管理消息),本文就是讨论的数据库上的管理。
此处为了使用更加方便,简化环境,采用更加轻量的数据库——SQLite,它是一个本地数据库,相当于直接操作本地的硬盘文件。
当在idea中配置好SQLiite数据库后,就需要建库建表,由于把配置依赖准备好之后就会自动的建库,因此我们这里主要关注的是建表,数据库存储的是交换机,队列和绑定,因此应该针对三者建立不同的表。可以根据之前创建的核心类进行设计表。那么上述的建表操作什么时机来执行,可能程序需要反复部署多次,为了简化部署的步骤,可以通过代码,自动完成建表的操作。
为了自动完成建表操作,首先创建一个接口,内有需要建表的方法,然后实现对应的xml文件,通过xml实现接口中的抽象方法。对与建表操作我们使用undata标签。最终,我们根据定义的类建立了三张表,但是对于其中的arguments,由于是Map属性, 为了把arguments 存到数据库中,需要把Map转化为json格式的字符串。
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface MetaMapper {
// 三个核心建表方法
void createExchangeTable();
void createQueueTable();
void createBindingTable();
}
当前,是把每个建表语句,都单独的列为一个 update 标签, 并且对应一个 java 方法,能否改成,一个 update 标签中包含多个 建表语句,同时借助一个 java 方法,完成上述多个表的创建呢? MyBatis 支持,一个 标签 中包含多个 sq| 语句的(前提是,搭配 MySQL 或者 Oracle).对于 SQLite,无法做到上述功能的,当你一个 update 标签中,写了多个 create table 语句的时候,只有第一个语句能执行.
如何实现把 arguments 这个键值对,和数据库中的字符串类型相互转换呢? 关键在于, MyBatis 在完成数据库操作的时候,会自动的调用到对象的 getter 和 setter.
- 比如 MyBatis 往数据库中写数据, 就会调用对象的 getter 方法,拿到属性的值,再往数据库中写。如果这个过程中,让 getArquments 得到的结果是 String 类型的,此时,就可以直接把这个数据写到数据库了
- 比如 MyBatis 从数据库读数据的时候,就会调用对象的 setter 方法,把数据库中读到的结果设置到对象的属性中.如果这个过程中,让 setArguments,参数是一个 String,并且在 setArquments 内部针对字符串解析,解析成一个 Map 对象
因此我们需要自己写Exchange类的getArguments和setArguments方法,其中getArguments用于MyBatis 往数据库中写数据,因此将Map转为Json类型的字符串。从数据库读数据之后,构造Exchange对象,会自动调用到setArguments,是把arguments从json格式的字符串转化为Map
第二个参数,用来描述当前 json 字符串, 要转成的 java 对象是什么类型的.如果是个简单类型,直接使用对应类型的类对象即可,如果是集合类这样的复杂类型,可以使用 TypeReference 匿名内部类对象,来描述复杂类型的具体信息,(通过泛型参数来描述的)
对于 交换机 和 队列 这两个表,由于使用 name 作为主键,直接按照 name 进行删除即可,对于绑定来说,此时没有主键,删除操作,其实是针对 exchangeName 和 queueName 两个维度进行筛选.。之后需要在接口中声明三个核心增删方法,然后需要在xml文件中写出insert和delete语句。如下:
其中的#{}:MyBatis 看到这个, 就会通过 getArguments 方法, 来获取到这个参数的内容,此处数据库中期望的类型是 String, 此处也就需要让 getArguments 能够得到 String。
此时,我们把数据库的基本操作已经借助MyBatis封装完成。接下来写一个类整合上面的操作。首先是数据库的初始化,此处使用的是一个普通的方法。数据库的初始化=建库建表 +插入一些默认数据,我们期望, 在咱们的 broker server 启动的时候, 做出下列逻辑判定:
1.如果数据库已经存在了,(表啥的都有了),不做任何操作.
2.如果数据库不存在, 则创建库,创建表,构造默认数据
数据库判断是否存在就判定 meta.db 这个文件是否存在即可。根据以上逻辑编写完成代码之后,发现一些方法涉及到mapper的相关调用,那么此时mapper需要保证是被构造出来的,那么如何进行实例化?Mapper是通过Mybatis进行操作的,换句话说,Mapper已经被注册到spring里面了,直接从spring里面拿到现成的对象。常用是@Autowired,但是前提是外面的类是一个注册在spring中的对象,但是现在并不打算让类是一个Bean对象,因为后面还需要手动进行管理,然后构造整体的结构,因此此时不可以用@Autowired,需要手动的构造。在启动类添加一个静态成员,在下面的main方法中,将run方法的返回结果赋值到静态成员,此时借助这个静态成员可以手动的获取指定的bean对象了。接下来在类中完成接口的三个核心insert和delete方法,可以增加select操作。最后进行测试。
设计单元测试要求,单元测试用例和用例之间是需要相互独立的,互不干扰的。因此可以这样子:每个用例执行之前,先执行一段逻辑,搭建测试的环境,准备好测试用的东西;每个用例执行之后,再执行一段逻辑,把用例执行过程中产生的中间结果的影响给消除掉。即“准备工作”和“收尾工作”,加上注解。
准备工作:对数据库进行初始化操作,由于init方法需要手动获取metaMapper,依赖于context对象,因此在测试用例中也需要context对象.
收尾工作:前面是数据库初始化,因此这里要清空数据库,在清空时注意此处不能直接就删除, 而需要先关闭上述 context 对象!! 此处的 context 对象, 持有了 MetaMapper 的实例, MetaMapper 实例又打开了 meta.db 数据库文件。如果 meta.db 被别人打开了, 此时的删除文件操作是不会成功的 (Windows 系统的限制, Linux 则没这个问题),另一方面, 获取 context 操作, 会占用 8080 端口. 此处的 close 也是释放 8080