• 消息队列的模拟实现(二)


    消息队列代码部分-(1)

    框架使用:Spring Boot,Mybatis

    软件:IDEA 2022.3.3社区版

    使用技术:Java、SQLite

    一、构思项目的组成

    详情查看链接: 消息队列的模拟实现(一)
    首先,实现消息队列这里主要分为三个模块:

    1. 公共模块
    • 主要存储公共类
    1. 服务器模块
    • 主要存储服务器方法,比如读取请求返回响应数据
    1. 客户端模块
    • 主要存储客户端方法,比如请求连接方法和构造请求

    根据上面的描述,我们创建一个Spring boot来搭建我们的项目。

    创建项目后添加依赖

    	<dependencies>
    		<dependency>
    			<groupId>org.springframework.bootgroupId>
    			<artifactId>spring-boot-starter-webartifactId>
    		dependency>
    		<dependency>
    			<groupId>org.mybatis.spring.bootgroupId>
    			<artifactId>mybatis-spring-boot-starterartifactId>
    			<version>2.3.1version>
    		dependency>
    
    		<dependency>
    			<groupId>org.springframework.bootgroupId>
    			<artifactId>spring-boot-starter-testartifactId>
    			<scope>testscope>
    		dependency>
    		<dependency>			<groupId>org.mybatis.spring.bootgroupId>
    			<artifactId>mybatis-spring-boot-starter-testartifactId>
    			<version>2.3.1version>
    			<scope>testscope>
    		dependency>
    		<dependency>
    			<groupId>org.projectlombokgroupId>
    			<artifactId>lombokartifactId>
    		dependency>
            
    		<dependency>
    			<groupId>org.xerialgroupId>
    			<artifactId>sqlite-jdbcartifactId>
    			<version>3.41.2.1version>
    		dependency>
    	dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    添加依赖后创建文件夹目录:

    在主目录下创建三个文件夹

    • common :公共类模块
    • mqclient :客户端模块
    • mqserver 服务器模块

    !在这里插入图片描述

    因为公共模块是创建总和其他模块公共方法,不宜作为第一选项实现模块,所以这里我们选择服务器的实现。

    建立交换机表、队列表和绑定表
    1. 设计数据库表

    对照核心类设置表,并理解属性的使用位置和属性含义。

    Exchange

    交换机表需要的属性也是根据我们需要的功能进行展开:

    • exchangeName :用于识别交换机
    • type:上面我们提到了交换机有三种类型、direct、fanout和topic
    • durable:持久化,数据的持久化
    • autodelete : 无人使用是否删除,节约内存空间
    • argument: argument中是创建一些交换机指定的一些额外的参数选项,后续代码中并没有对应的参数,相对于一个扩展参数

    mqServer创建一个文件夹用于存储核心类,该核心主要是一些实体类。

    分析完交换机的字段,创建一个交换机类:

    import lombok.Data;
    import java.util.HashMap;
    import java.util.Map;
    
    @Data//添加setter和getter 方法
    public class Exchange {
        private String name;
        private ExchangeType type = ExchangeType.DIRECT;
        private boolean durable = false;//持久化存储
        private boolean autoDelete = false;//自动删除
        //扩展属性
        private Map<String,Object> arguments = new HashMap<>();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在创建交换机时,我们将设定交换机类型,这里建议使用枚举,创建一个交换机类型枚举。取名为ExechangeType同样是放在核心文件夹中。

    //这个类用于存储三种类型
    public enum ExchangeType {
        DIRECT(0),
        FANOUT(1),
        TOPIC(2);
        private final int type;
    
        ExchangeType(int type) {
            this.type = type;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    Queue

    接下来分析队列需要的字段名称:

    • queueName:队列名,用于识别队列
    • durable :数据是否需要持久化设置
    • exclusive :独占功能,开启独占只能给一个消费者使用
    • autoDelete:无人使用是否删除,节约内存空间
    • argument:扩展参数,先不实现

    同样的在核心文件夹创建一个queue类,这里需要注意这个名字与Java中的集合类名字相同,这里因为是存储消息的队列,所以改名为MSGQueue

    import lombok.Data;
    import java.util.HashMap;
    import java.util.Map;
    
    @Data
    public class MSGQueue {
        private String name;//队列名称
        private boolean durable;//队列持久化设置
        private boolean exclusive;//对列只能属于一个消费者
        private boolean autoDelete;//对不使用的队列自动删除
        //扩展功能
        private Map<String,Object> argument = new HashMap<>();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    Binding

    要分析绑定,就应该知道绑定的作用,绑定的作用是用来将交换机和队列进行绑定,更好的传输数据。

    • exchangeName:交换机名称
    • queueName:队列名称
    • bindingKey :当交换机为topic时使用匹配时的机制
    import lombok.Data;
    
    @Data
    public class Binding {
        private String exchangeName;//绑定的交换机名称
        private String queueName;//绑定的队列名称
        private String bindingKey;//当交换机是topic类型时需要进行匹配时使用的字段
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    Message

    Message 中需要实现Serializable接⼝.

    这个接口是用来管理序列化和反序列化。对象的序列化是指将对象转换成字节流的过程,可以将序列化的对象存储到硬盘中或通过网络传输。而反序列化是将字节流转化为对象的过程。

    后续需要把 Message? 写⼊⽂件以及进⾏⽹络传输

    • BasicProperties :消息属性,需要额外写一个类表示
    • byte[] : 消息体,消息的具体内容
    • offsetBeg :消息体开始的地方在文件中相当于一个下标
    • offsetEnd:消息结束的地方
    • isValid:标记消息的有效性,0x1 是有效 0x0是无效
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import lombok.experimental.Accessors;
    
    import javax.xml.validation.ValidatorHandler;
    import java.io.Serializable;
    import java.util.UUID;
    
    @Data
    public class Message implements Serializable {
        //验证开发者版本号
        private static final long serialVersionUID = 1L;
        //Message中核心部分
        private BasicProperties basicProperties = new BasicProperties();
        private byte[] body;//正文部分
        /*
        使用两个偏移量来表示某个消息存储在文件中的位置
        使用前闭后开模式[offsetBeg,offsetEnd]
         */
        private long offsetBeg = 0;//开始
        private long offsetEnd = 0;//结尾
        //使用一个变量标记文件是否有效
        // 0x1 -> 有效  0x0 -> 无效
        private byte isValid = 0x1;
        //创建一个工厂方法,让工厂方法帮我们封装一个 Message 对象
        //在这个方法中获取一个UUID 以 M- 作为消息前缀
        public static Message createMessage(String routingKey,BasicProperties basicProperties,byte[] body){
            Message message = new Message();
            if(basicProperties != null){
                message.setBasicProperties(basicProperties);
            }
            //UUID 的生成
            message.basicProperties.setMessageId("M-"+ UUID.randomUUID());
            message.basicProperties.setRoutingKey(routingKey);
            message.setBody(body);
            //只设置两个属性,一个body ,一个 basicProperties
           return message;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    实现信息属性类

    里面包含了信息ID、消息匹配令牌和持久化数据,注意因为是由message引用所有也需要添加Serializable接口。

    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import lombok.experimental.Accessors;
    
    import java.io.Serializable;
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Accessors(chain = true)
    public class BasicProperties implements Serializable {
        //为了保障消息的唯一性
        private String messageId;
        //用于匹配政策
        private String routingKey;
        //持久化设置 1-》不持久化   2-》持久化
        private int deliverMode = 1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    Sqlite中建表

    Sqlite中建表,在资源文件中创建一个mateMapper.xml文件用编写创建表格的代码或者修改表格的语句。

    这里是将每一个建表语句都分开使用update来建表,能否改为一个update来创建多个表?借助一个方法创建多个表创建?

    答:不可以,原因是因为 SQLite 和 MySQL是不一样的,只能执行第一条语句,后续语句直接忽略,所以每执行一次,都需要额外再写一次SQL语句。

    SQL建表语句

    根据字段进行创建,以下分别是交换机、队列和绑定表的创建。

    <update id="createExchangeTable">
        create table if not exists exchange(
        name varchar(50) primary key,
        type int,
        durable boolean,
        autoDelete boolean,
        argument varchar(1024),
        )
    update>
    <update id="createQueueTable">
        create table if not exists queue(
        name varchar(50) primary key,
        durable boolean,
        exclusive boolean,
        AutoDelete boolean,
        argument varchar(1024)
        )
    update>
    <update id="createBindingTable">
        create table if not exists binding(
        exchangeName varchar(50),
        queueName varchar(50),
        bindingKey varchar(256)
        )
    update>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    实现接口调用建表操作

    当添加了sql语句后,我们应该在代码实现一个接口来调用文件中的方法。

    创建一个专门存放映射接口的文件夹叫mapper,创建一个mateMapper类作为映射关系来调用MateMapper.xml中的方法。

    import com.example.mq.mqserver.core.Binding;
    import com.example.mq.mqserver.core.Exchange;
    import com.example.mq.mqserver.core.MSGQueue;
    import org.apache.ibatis.annotations.Mapper;
    
    import java.util.List;
    import java.util.concurrent.Executors;
    
    @Mapper
    public interface MateMapper {
        //创建三个表
        void createExchangeTable();
        void createQueueTable();
        void createBindingTable();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    Map数据结构的序列化

    Mybatis在操作数据库时,读取数据库数据时会调用Getter方法,我们只需要在这个读取数据的过程中,使得数据是字符串即可,同样的,在设置这个数据时,会调用Setter方法,以同样的方法去修改即可,总而言之在设置Map时将传递数据的格式改为字符串,修改类中的getter和setter方法。

    将map参数转换为字符串存储,再将字符串数据转化为Json数据格式使用读取。修改getter方法和setter方法。将序列化代码添加到Exchange、MSGQueue等存在 Map 数据结构的类中。

    1. 这里需要使用到ObjectMapper所以需要添加依赖
    <dependency>
        <groupId>com.fasterxml.jackson.coregroupId>
        <artifactId>jackson-databindartifactId>
        <version>2.15.2version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 然后使用ObjectMapper中转换字符串的方法。相对于一个序列化和反序列化的过程。
        public String getArguments() {
            //将当前的argument参数修改成String(Json)
            ObjectMapper objectMapper =new ObjectMapper();
            try {
                //转化为json
                return objectMapper.writeValueAsString(arguments);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            //如果出现异常,则返回一个空字符串即可
            return "{}";
        }
        //这个方法,是从数据库中读取数据后,构造Exchange对象,会自动调用的方法
        public void setArguments(String argumentsJson) {
            //把参数中的argumentJson解析,转换成一个map对象
            ObjectMapper objectMapper = new ObjectMapper();
            try {
                objectMapper.readValue(argumentsJson, new TypeReference<HashMap< String, Object >>() {
                });
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    new TypeReference>() {}参数含义:

    用于描述当前的json字符串转化为字符串类型,使用方法:

    • 简单类型:直接使用对应类型即可
    • 集合这种的复杂类型:使用TypeReference匿名内部类来表示复杂类型的具体信息。

    添加增加和删除方法并实现SQL语句。

    TypeReference类的意义:

    是Java的一种泛型类,主要用于获取泛型中具体类型的信息,并在需要时恢复泛型的信息。

    创建数据库和连接数据库

    这里提供了两种方法来连接数据库。这里我将数据库名称取名为mate.db

    编写一个yml文件:

    spring:
      datasource:
        url: jdbc:sqlite:./MessageData/meta.db #相对路径的写法
        username:
        password:
        driver-class-name: org.sqlite.JDBC
      # sqlite不需要写用户名和密码,严密性没有MySql强
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    编写一个properties文件

    #连接 sqlite 数据库
    spring.datasource.url=jdbc:sqlite:./data/mate.db
    spring.datasource.username=
    spring.datasource.password=
    spring.datasource.driver-class-name=org.sqlite.JDBC
    #这里不需要填写用户名和密码,因为是单用户数据库
    
    #设置mybatis的接口文件
    mybatis.mapper-locations=classpath:mapper/**Mapper.xml
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    可以随意使用,所以这里两种文件的区别也可以一眼看出。

    添加数据

    在之前的代码中,进行了建表操作,接下来我们对接口中数据进行一些功能添加。

    • 添加一些初始化数据
    • 在程序启动时,做出逻辑判断
      • 数据库存在,无操作动作
      • 数据库不存在,创建库创建表(如何判断?查询数据库mate.db文件存在性即可)
    添加接口方法操作数据库

    首先是对表中添加数据操作,可以先在接口中实现API,然后再添加sql语句。

        //添加数据操作
        void addExchange(Exchange exchange);
        void addQueue(MSGQueue queue);
        void addBinding(Binding binding);
    	//删除数据操作
    	void deleteExchange(String exchangeName);//交换机根据交换机名称删除
        void deleteQueue(String queueName);//队列根据队列名称删除
        void deleteBinding(String ExchangeName, String queueName);//绑定根据交换机和队列的名称删除
    //查询操作分为查询一个或者查询所有
        //先实现查询所有数据
        List<Exchange> selectAllExchange();
        List<MSGQueue> selectAllQueue();
        List<Binding> selectAllBinding();
        //查询一个
        Exchange selectExchange(String exchangeName);
        MSGQueue selectQueue(String queueName);
        Binding selectBinding(Binding binding);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    MateMapper.xml中实现操作sqlite语句

    Mybatis字段名含义

    1. id:方法名称对应到接口中的方法名
    2. parameterType:对调用mapper接口的使用的参数类型
    3. resultType:查询结果的参数类型
    1. 添加数据
    2. 删除数据
    3. 查找数据
    
        <insert id="addExchange" parameterType="com.example.mq.mqserver.core.Exchange">
            insert into exchange value(#{name},#{type},#{durable},#{autoDelete},#{argument})
        insert>
        <insert id="addQueue" parameterType="com.example.mq.mqserver.core.MSGQueue">
            insert into queue value(#{name},#{durable},#{exclusive},#{autoDelete},#{argument})
        insert>
        <insert id="addBinding" parameterType="com.example.mq.mqserver.core.Binding">
            insert into binding value(#{exchangeName},#{queueName),#{BindingKey});
        insert>
        
        <delete id="deleteExchange" parameterType="com.example.mq.mqserver.core.Exchange">
            delete from exchange where name = #{exchangeName};
        delete>
        <delete id="deleteQueue" parameterType="com.example.mq.mqserver.core.MSGQueue">
            delete from queue where name = #{queueName};
        delete>
    <delete id="deleteBinding" parameterType="com.example.mq.mqserver.core.Binding">
            delete from binding where exchangeName = #{exchangeName} and queueName = #{queueName};
        delete>
        
        <select id="selectAllExchange" resultType="com.example.mq.mqserver.core.Exchange">
            select * from exchange
        select>
        <select id="selectAllQueue" resultType="com.example.mq.mqserver.core.MSGQueue">
            select * from queue;
        select>
        <select id="selectAllBinding" resultType="com.example.mq.mqserver.core.Binding">
            select * from binding
        select>
        <select id="selectExchange" resultType="com.example.mq.mqserver.core.Exchange">
            select * from exchange where name = #{exchangeName};
        select>
        <select id="selectQueue" resultType="com.example.mq.mqserver.core.MSGQueue">
            select * from queue where name = #{queueNAme};
        select>
        <select id="selectBinding" resultType="com.example.mq.mqserver.core.Binding">
            select * from binding where exchangeName = #{exchangeName} and queueName = #{queueName};
        select>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    使用DataBaseManger总和

    以上就是我们第一步的创建工作,接下来使用DataBaseManger来总和上述功能。

    mqserver中创建一个文件夹名为datacenter(数据中心),文件夹创建一个类,类名为DataBaseManger,然后使用这个类来调用我们刚刚创建的方法,创建数据库文件、删除数据库文件和建表等操作。

    整合步骤

    1. 创建一个 MateMapper
    2. 创建初始化方法,
      1. 检查数据库可靠性,不存在就创建一个,
      2. 添加默认交换机
      3. 添加数据库文件
      4. 建表操作
    3. 删除数据库文件(这里为了方便后面代码的添加,提前实现)
    import com.example.mq.MqApplication;
    import com.example.mq.mqserver.mapper.MateMapper;
    
    import java.io.File;
    
    public class DataBaseManger {
        private MateMapper mateMapper;
    
        //初始化
        public void init(){
            //获取bean 对象
            mateMapper = MqApplication.context.getBean(MateMapper.class);
            //检查数据库的存在性
            if(! checkDBExists()){
                //数据库不存在,进行以下操作
                // 1. 创建目录
                File dataFile = new File("./data");
                dataFile.mkdirs();
                // 2. 创建数据库
                cretaeTable();//将三个表对封装在里面
                // 3. 插入默认数据
                creataDefaultData();//添加一个默认的交换机,这是因为 RabbitMq中有一个
                System.out.println("[DataBAseManger]第一阶段 数据库初始化完成!");
            }else {
                System.out.println("[DataBaseManger]第一阶段 数据库已经存在!");
            }
        }
    
        private boolean checkDBExists() {
            File file = new File("/data/mate.db");
            return file.exists();
        }
        //删除数据库文件
        public void deleteDataBase(){
            //1. 删除文件
            //2. 删除目录
            File file = new File("./data/mte.db");
            boolean delete = file.delete();
            if(delete){
                System.out.println("[DataBaseManger] 删除数据库成功!");
            }else {
                System.out.println("[DataBaseManger] 删除数据库失败!");
            }
            File deleteDir = new File("./data");
            delete = deleteDir.delete();
            if(delete){
                System.out.println("[DataBaseMAnger] 删除数据库目录成功!");
            }else {
                System.out.println("[DataBaseMAnger] 删除数据库目录失败!");
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    这里我们添加两个方法给上面代码使用

    创建表工作:

    private void createTable() {
        mateMapper.createExchangeTable();
        mateMapper.createQueueTable();
        mateMapper.createExchangeTable();
        System.out.println("[DataBaseMange]第一阶段 建表完成!");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    添加默认交换机数据

    private void createDefaultData() {
        Exchange exchange = new Exchange();
        exchange.setName("");
        exchange.setType(ExchangeType.DIRECT);
        exchange.setDurable(false);
        exchange.setAutoDelete(false);
        System.out.println("[DataBaseManger] 第一阶段 添加默认数据成功 ");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    最后将系统中的后续添加的方法写入代码中。

    //添加程序后续方法
        public void addExchange(Exchange exchange){
            mateMapper.addExchange(exchange);
        }
        public void addQueue(MSGQueue queue){
            mateMapper.addQueue(queue);
        }
        public void addBinding(Binding binding){
            mateMapper.addBinding(binding);
        }
        public void deleteExchange(String exchangeName){
            mateMapper.deleteExchange(exchangeName);
        }
        public void deleteQueue(String queueName){
            mateMapper.deleteQueue(queueName);
        }
        public void deleteBinding(Binding binding){
            mateMapper.deleteBinding(binding);
        }
        public List<Exchange> selectAllExchange(){
            return mateMapper.selectAllExchange();
        }
        public List<MSGQueue> selectAllQueue(){
            return mateMapper.selectAllQueue();
        }
        public List<Binding> selectAllBinding(){
            return mateMapper.selectAllBinding();
        }
        public Exchange selectExchange(String exchangeName){
            return mateMapper.selectExchange(exchangeName);
        }
        public MSGQueue selectQueue(String queueName){
            return mateMapper.selectQueue(queueName);
        }
        public Binding selectBinding(Binding binding){
            return mateMapper.selectBinding(binding);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    接下来我们将对这个整合类进行单元测试。

    单元测试

    注释解读:

    @BeforEach:每执行一个测试方法之前执行一次(一般用于初始化一个对象)

    @AfterEach:每执行完一个测试方法后执行一次(一般用于关闭资源)

    针对DataBaseMAnger进行一次单元测试。

    单元测试用例和用例是互不干扰的,相互独立。

    测试目标:

    1. 将三个模块的增删改查进行测试和修改
    2. 达到预期目标

    首先我们创建一个 dataBaseManger类以便后续调用,添加两个方法来创建数据库对象和删除数据库对象。

    import com.example.mq.mqserver.datacenter.DataBaseManger;
    import org.junit.jupiter.api.AfterEach;
    import org.junit.jupiter.api.BeforeEach;
    import org.springframework.boot.SpringApplication;
    @SpringBootTest
    public class DataBaseMangerTest {
        private DataBaseManger dataBaseManger;
        @BeforeEach
        public void setUp(){
            //通过 SpringApplication 得到 mataMapper 实例
            MqApplication.context = SpringApplication.run(MqApplication.class);
            //初始化dataBaseManger对象  
            dataBaseManger.init();
        }
        @AfterEach
        public void tearDown(){
            //1.关闭 context 对象
            //2. 删除数据库文件
            MqApplication.context.close();
            dataBaseManger.deleteDataBase();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    测试初始化

    在使用了初始化方法后,我们需要测试在初始化方法中的建表功能和添加默认数据是否成功。

    @Test
    public void testInit(){
        //检查数据库状态,查询数据库中存在该表
        List<Exchange> exchanges = dataBaseManger.selectAllExchange();
        List<MSGQueue> msgQueues = dataBaseManger.selectAllQueue();
        List<Binding> bindings = dataBaseManger.selectAllBinding();
        //使用断言来判断表中数据
        Assertions.assertEquals(1,exchanges.size());
        //如果查询到存在数据,需要判断数据是不是我们默认添加的那一条
        Assertions.assertEquals("",exchanges.get(0).getName());
        Assertions.assertEquals(ExchangeType.DIRECT,exchanges.get(0).getType());
        Assertions.assertTrue(exchanges.get(0).isDurable());
        Assertions.assertEquals(0,msgQueues.size());
        Assertions.assertEquals(0,bindings.size());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    结果:

    在这里插入图片描述

    创建与删除交换机
    1. 设置一个方法对一个交换机进行实例化
    2. 编写测试代码
    3. 将刚刚的交换机引入,然后进行添加到数据库操作
    4. 查询数据库中交换机是否和本地交换机相同
    5. 删除交换机,再次查询数据库中的交换机,是否为空

    代码实现:

     public Exchange createExchangeTest(String exchangeName){
            Exchange exchange = new Exchange();
            exchange.setName(exchangeName);
            exchange.setType(ExchangeType.DIRECT);
            exchange.setDurable(true);
            exchange.setAutoDelete(false);
    
            return exchange;
        }
    
        @Test
        public void testExchange(){
            //1.构造一个交换机并插入
            Exchange expectExchange = createExchangeTest(exchangeName);
            dataBaseManger.addExchange(expectExchange);
            //2.查询这个交换机,比较结果比较是否一致
            Exchange actualExchange = dataBaseManger.selectExchange(exchangeName);
            Assertions.assertEquals(expectExchange,actualExchange);
            //3.删除这个交换机
            dataBaseManger.deleteExchange(exchangeName);
            //4.测试交换机是否存在
            actualExchange = dataBaseManger.selectExchange(exchangeName);
            Assertions.assertNull(actualExchange);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在这里插入图片描述

    创建队列

    代码实现:

    public MSGQueue createQueueTest(String queueName){
        MSGQueue msgQueue = new MSGQueue();
        msgQueue.setName(queueName);
        msgQueue.setDurable(true);
        msgQueue.setExclusive(false);
        msgQueue.setAutoDelete(false);
        return msgQueue;
    }
    @Test
    public void testQueue(){
        //1. 创建一个队列并添加到数据库中
        MSGQueue expectQueue = createQueueTest(queueName);
        dataBaseManger.addQueue(expectQueue);
        //2.查询数据库中姓名为 queueName 的队列
        MSGQueue actualQueue = dataBaseManger.selectQueue(queueName);
        //3.创建的队列和数据库中进行对比
        Assertions.assertEquals(expectQueue,actualQueue);
        //4.删除这个队列
        dataBaseManger.deleteQueue(queueName);
        //5.再次查询同样队列名称,是否为 null
        actualQueue = dataBaseManger.selectQueue(queueName);
        Assertions.assertNull(actualQueue);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在这里插入图片描述

    创建绑定

    代码实现:

    //创建绑定需要一个交换机和一个队列
    public Binding creatTestBinding(String exchangeName,String queueName){
        Binding binding = new Binding();
        binding.setExchangeName(exchangeName);
        binding.setQueueName(queueName);
        binding.setBindingKey("testBindKeys");
        return binding;
    }
    @Test
    public void bindTest(){
        //1.添加绑定到数据库中
        Binding expectBind = creatTestBinding(exchangeName, queueName);
        dataBaseManger.addBinding(expectBind);
        //2. 查询绑定数据然后对比数据
        Binding actualBind = dataBaseManger.selectBinding(expectBind);
        //3. 对比数据是否相同
        Assertions.assertEquals(expectBind,actualBind);
        //4. 删除绑定
        dataBaseManger.deleteBinding(actualBind);
        //5.查询绑定并比较数据
        actualBind = dataBaseManger.selectBinding(expectBind);
        Assertions.assertNull(actualBind);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在这里插入图片描述

  • 相关阅读:
    mongostat性能分析
    网络——流量控制&可靠传输&滑动窗口
    Debian10离线安装docker-20.10.13
    开源OLAP数据库ClickHouse获2.5亿美元B轮融资
    字符串Hash学习笔记
    小趴菜实战Mac上安装Anaconda
    【pnpm】从了解到应用
    ufw配置:外网ip禁止访问配置
    jquery ajax提交 jquery post提交表单 更新页面
    YOLOv5论文作图教程(2)— 软件界面布局和基础功能介绍
  • 原文地址:https://blog.csdn.net/m0_51945115/article/details/132895880