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());
    }
相关推荐
点光11 小时前
使用Sentinel作为Spring Boot应用限流组件
后端
不要秃头啊11 小时前
别再谈提效了:AI 时代的开发范式本质变了
前端·后端·程序员
有志12 小时前
Java 项目添加慢 SQL 查询工具实践
后端
山佳的山12 小时前
KingbaseES 共享锁(SHARE)与排他锁(EXCLUSIVE)详解及测试复现
后端
Leo89912 小时前
rust 从零单排 之 一战到底
后端
程序员清风13 小时前
程序员兼职必看:靠谱软件外包平台挑选指南与避坑清单!
java·后端·面试
鱼人14 小时前
MySQL 实战入门:从“增删改查”到“高效查询”的核心指南
后端
大鹏198814 小时前
告别 Session:Spring Boot 实现 JWT 无状态登录认证全攻略
后端
Java编程爱好者14 小时前
从 AQS 到 ReentrantLock:搞懂同步队列与条件队列,这一篇就够了
后端
鱼人14 小时前
Nginx 全能指南:从反向代理到负载均衡,一篇打通任督二脉
后端