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

2023-09-15 10:09:32

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

框架使用:Spring Boot,Mybatis

软件:IDEA 2022.3.3社区版

使用技术:Java、SQLite

一、构思项目的组成

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

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

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

创建项目后添加依赖

	<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>2.3.1</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>2.3.1</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>
        <!-- 这个依赖是用于与 sqlite 数据库进行交互的 Java 库。它提供了一组用于连接,查询和操作SQLite数据库的API和工具-->
		<dependency>
			<groupId>org.xerial</groupId>
			<artifactId>sqlite-jdbc</artifactId>
			<version>3.41.2.1</version>
		</dependency>
	</dependencies>

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

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

  • 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<>();
}

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

//这个类用于存储三种类型
public enum ExchangeType {
    DIRECT(0),
    FANOUT(1),
    TOPIC(2);
    private final int type;

    ExchangeType(int type) {
        this.type = type;
    }
}
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<>();
}
Binding

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

  • exchangeName:交换机名称
  • queueName:队列名称
  • bindingKey :当交换机为topic时使用匹配时的机制
import lombok.Data;

@Data
public class Binding {
    private String exchangeName;//绑定的交换机名称
    private String queueName;//绑定的队列名称
    private String bindingKey;//当交换机是topic类型时需要进行匹配时使用的字段
}
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;
    }
}
实现信息属性类

里面包含了信息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;
}

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>
实现接口调用建表操作

当添加了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();
}
Map数据结构的序列化

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

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

  1. 这里需要使用到ObjectMapper所以需要添加依赖
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.2</version>
</dependency>
  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();
        }
    }
}

new TypeReference<HashMap< String, Object >>() {}参数含义:

用于描述当前的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强

编写一个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

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

添加数据

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

  • 添加一些初始化数据
  • 在程序启动时,做出逻辑判断
    • 数据库存在,无操作动作
    • 数据库不存在,创建库创建表(如何判断?查询数据库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);
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>
使用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] 删除数据库目录失败!");
        }
    }
}

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

创建表工作:

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

添加默认交换机数据

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

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

//添加程序后续方法
    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);
    }

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

单元测试

注释解读:

@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();
    }
}
测试初始化

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

@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. 删除交换机,再次查询数据库中的交换机,是否为空

代码实现:

 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);
    }

在这里插入图片描述

创建队列

代码实现:

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);
}

在这里插入图片描述

创建绑定

代码实现:

//创建绑定需要一个交换机和一个队列
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);
}

在这里插入图片描述

更多推荐

Window 10安装MySQL 5.7

1、访问如下链接进行下载:Mysql官方下载地址官方地址下载的东西没有那么多病毒~。这东西见仁见智吧哈哈~。有些人不怕这个。注意安装上面这个下面的是test2、填写mysql的环境变量具体操作步骤如下:在桌面按下快捷键“Win+R”输入“controlsysdm.cpl,EnvironmentVariables”并点击

HEC-RAS 1D/2D水动力与水环境模拟教程

详情点击公众号技术科研吧链接:HEC-RAS1D/2D水动力与水环境模拟教程前言水动力与水环境模型的数值模拟是实现水资源规划、环境影响分析、防洪规划以及未来气候变化下预测和分析的主要手段。然而,一方面水动力和水环境模型的使用非常复杂,理论繁复;另一方面,免费的水动力和水环境软件往往缺少重要功能,而商业软件则非常昂贵。H

泡泡玛特海外布局动作不断,开启东南亚潮玩盛会

近日,泡泡玛特海外布局动作不断,9月8日至10日,泡泡玛特2023PTS潮流玩具展(下简称新加坡PTS)在新加坡滨海湾金沙成功举办,现场人气爆棚,三天吸引了超过2万观众入场,这也是泡泡玛特首次在海外举办大型潮玩展。9月20日,泡泡玛特泰国首店盛大开业,吸引超千名粉丝现场排队,并在当地社交媒体引发热议。实际上,泡泡玛特泰

python浮点数比较的三种方法

在Python中,由于浮点数在计算机内部的表示方式是二进制的,因此进行浮点数比较时可能会出现精度问题。为了解决这个问题,你可以使用以下几种方法来比较浮点数:使用近似比较:通过设置一个小的误差范围,在比较两个浮点数时,判断它们的差是否在这个误差范围内。例如,你可以定义一个epsilon值(即允许的误差范围)来比较两个浮点

国产自研BI系统,更懂中国企业数据分析需求

国产自研BI系统是指由中国企业自主研发的商业智能(BI)系统,这类系统更加了解中国企业的数据分析需求,能够提供更加贴合实际的解决方案。比如说奥威BI系统就是典型的国产自研,不仅了解中国企业的数据分析需求,还根据多年的经验为中国企业量身打造了多套标准化的BI数据分析解决方案,为中国企业提供更低风险、高效性、高性价比的智能

分布式锁实现方法

分布式锁什么时候需要加锁有并发,多线程有写操作有竞争关系场景:电商系统,下单流程:用户下单–>秒杀系统检查redis商品库存信息–>用户锁定并更新库存(mysql)—>秒杀系统更新redis问题:单机部署,单线程执行无问题,多线程并发操作会引起超卖解决:对用户下单后的步骤加锁,让线程排队,避免超卖(synchroniz

Caddy Web服务器深度解析与对比:Caddy vs. Nginx vs. Apache

🌷🍁博主猫头虎带您GotoNewWorld.✨🍁🦄博客首页——猫头虎的博客🎐🐳《面试题大全专栏》文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺🌊《IDEA开发秘籍专栏》学会IDEA常用操作,工作效率翻倍~💐🌊《100天精通Golang(基础入门篇)》学会Golang语言,畅玩云原生,走遍大

电脑桌面透明便签软件是哪个?

在现代快节奏的工作环境中,许多上班族都希望能够在电脑桌面上方便地记录工作资料、重要事项、工作流程等内容。为了解决这个问题,一款优秀的电脑桌面便签软件是必不可少的。在选择桌面便签软件时,许多用户也希望便签软件能够与电脑桌面壁纸相统一,提升桌面美观度。那么,桌面便签软件哪个好用?有没有一款支持透明皮肤的电脑桌面便签软件值得

Java对比对象修改前与修改后字段发生的变化

开发过程中,我们通常会对系统操作人对系统的操作进行记录,记录操作前后某个字段的变化,如下图2.提供一个工具类,可以比较一个对象里面,源对象,与修改后的对象,有哪些字段发生了改变,第一步/***@authorqiankun.hu*@version1.0.0*@createTime2023年09月20日17:00:00*@

“MicroPython在微控制器上实现I2S支持:详细指南与完整Python示例代码

第一部分:MicroPython与微控制器简介1.MicroPython简介MicroPython是Python3的精简、高效、快速实现,专为微控制器和受限制的环境设计。与传统的Python相比,MicroPython是为了在资源受限的设备上运行而优化的,这意味着它可以在只有几十KB的RAM和存储空间的设备上运行。2.

Python的多重继承和MixIn

前言:嗨喽~大家好呀,这里是魔王呐❤~!python更多源码/资料/解答/教程等点击此处跳转文末名片免费获取多重继承继承是面向对象编程的一个重要的方式,因为通过继承,子类就可以扩展父类的功能。回忆一下Animal类层次的设计,假设我们要实现以下4种动物:Dog-狗狗;Bat-蝙蝠;Parrot-鹦鹉;Ostrich-鸵

热文推荐