SpringBoot 项目学习内容详解(二)

考虑数据库的使用:

本次项目采用的是相比于 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());
    }
相关推荐
世人万千丶1 天前
Flutter 框架跨平台鸿蒙开发 - 恐惧清单应用
学习·flutter·华为·开源·harmonyos·鸿蒙
涡能增压发动积1 天前
同样的代码循环 10次正常 循环 100次就抛异常?自定义 Comparator 的 bug 让我丢尽颜面
后端
yuzhuanhei1 天前
Visual Studio 配置C++opencv
c++·学习·visual studio
Wenweno0o1 天前
0基础Go语言Eino框架智能体实战-chatModel
开发语言·后端·golang
swg3213211 天前
Spring Boot 3.X Oauth2 认证服务与资源服务
java·spring boot·后端
tyung1 天前
一个 main.go 搞定协作白板:你画一笔,全世界都看见
后端·go
gelald1 天前
SpringBoot - 自动配置原理
java·spring boot·后端
@yanyu6661 天前
07-引入element布局及spring boot完善后端
javascript·vue.js·spring boot
殷紫川1 天前
深入拆解 Java 内存模型:从原子性、可见性到有序性,彻底搞懂 happen-before 规则
java·后端
元宝骑士1 天前
FIND_IN_SET使用指南:场景、优缺点与MySQL优化策略
后端·mysql