2024.2.6 模拟实现 RabbitMQ —— 数据库操作

目录

引言

选择数据库

环境配置

设计数据库表

实现流程

封装数据库操作

针对 DataBaseManager 单元测试


引言

  • 硬盘保存分为两个部分
  1. 数据库:交换机(Exchange)、队列(Queue)、绑定(Binding)
  2. 文件:消息(Message)

选择数据库

  • MySQL 数据库是比较重量的数据库!
  • 此处为了使用更方便,简化环境,采取的数据库是更轻量的 SQLite 数据库

原因:

  1. 一个完整的 SQLite 数据库,只有一个单独的可执行文件(不到 1M)
  2. MySQL 是客户端服务器结构的程序,而 SQLite 只是一个本地的数据库,相当于是直接操作本地的硬盘文件

注意:

  • SQLite 数据库应用非常广泛,在一些性能不高的设备上,SQLite 数据库是首选
  • 尤其是移动端和嵌入式设备 (Android 系统就是内置的 SQLite)

环境配置

  • 在 Java 中要想使用 SQLite 数据库,无需额外安装,直接使用 Maven,将 SQLite 的依赖直接引入进来即可!
  • 此时 Maven 依赖会自动加载 jar 包和 动态库文件
<!-- https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc -->
<dependency>
    <groupId>org.xerial</groupId>
    <artifactId>sqlite-jdbc</artifactId>
    <version>3.41.0.1</version>
</dependency>
  • 编写 yml 配置文件(此处我们使用 MyBatis 操作 SQLite 数据库)
spring:
  datasource:
    url: jdbc:sqlite:./data/meta.db
    username:
    password:
    driver-class-name: org.sqlite.JDBC

mybatis:
  mapper-locations: classpath:mapper/**Mapper.xml

注意点一:

  • SQLite 数据库将数据存储在当前硬盘的某个指定的文件中(./data/meta.db)

注意点二:

  • 谈到相对路径,就需要明确 "基准路径" "工作路径"
  • 如果是在 IDEA 中直接运行程序,此时工作路径就是当前项目所在的路径
  • 如果是通过 javr -jar 方式运行部署的,此时你在哪个目录下执行的命令,哪个目录就是工作路径

注意点三:

  • 对于 SQLite 数据库来说,并不需要指定用户名密码
  • MySQL 数据库是一个客户端服务器结构的程序,而一个数据库服务器,就会对应很多个客户端来访问它
  • 相比之下,SQLite 则不是客户端服务器结构的程序,其数据放在本地文件上,与网络无关,只有本地主机才能访问

注意点四:

  • SQLite 虽然和 MySQL 不太一样,但是都可以通过 MyBatis 这样的框架来使用

注意点五:

  •  当把上述的配置和依赖都准备好了之后,程序启动便会自动建库!

设计数据库表

  • 需要在数据库中存储的有 交换机(Exchange)、队列(Queue)、绑定(Binding)
  • 对照着上述这样的核心类,很容易把这几个表设计出来的

问题:

  • 上述表的建表操作,具体什么时机来执行?

回答:

  • 以往写的程序,都是先将数据库表啥的创建好,再启动服务器
  • 即 将建库建表语句写到一个 .sql 文件中,需要建表时,直接复制到 MySQL 客户端中执行即可
  • 这个操作都是在部署阶段完成的
  • 之前大概就部署一次即可,不会反复操作,但是后续接触到的更多的程序可能会涉及到反复部署多次
  • 综上,通过代码自动完成建表操作,简化部署步骤,也是十分关键的!

实现流程

  • 创建一个 interface 接口,描述有哪些方法要给 Java 代码使用
import com.example.demo.mqserver.core.Binding;
import com.example.demo.mqserver.core.Exchange;
import com.example.demo.mqserver.core.MSGQueue;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface MetaMapper {
//    提供三个核心建表方法
    void createExchangeTable();
    void createQueueTable();
    void createBindingTable();

//    针对上述三个基本概念,进行 插入 和 删除
    void insertExchange(Exchange exchange);
    List<Exchange> selectAllExchanges();
    void deleteExchange(String exchangeName);
    void insertQueue(MSGQueue msgQueue);
    List<MSGQueue> selectAllQueues();
    void deleteQueue(String queueName);

    void insertBinding(Binding binding);
    List<Binding> selectAllBindings();
    void deleteBinding(Binding binding);
}
  • 创建对应的 xml 文件,通过 xml 来实现上述 interface 接口中的抽象方法
<?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.example.demo.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.example.demo.mqserver.core.Exchange">
        insert into exchange values(#{name}, #{type}, #{durable}, #{autoDelete}, #{arguments});
    </insert>

    <select id="selectAllExchanges" resultType="com.example.demo.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.example.demo.mqserver.core.MSGQueue">
        insert into queue values(#{name}, #{durable}, #{exclusive}, #{autoDelete}, #{arguments});
    </insert>

    <select id="selectAllQueues" resultType="com.example.demo.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.example.demo.mqserver.core.Binding">
        insert into binding values(#{exchangeName}, #{queueName}, #{bindingKey});
    </insert>

    <select id="selectAllBindings" resultType="com.example.demo.mqserver.core.Binding">
        select * from binding;
    </select>

    <delete id="deleteBinding" parameterType="com.example.demo.mqserver.core.Binding">
        delete from binding where exchangeName = #{exchangeName} and queueName = #{queueName};
    </delete>
</mapper>

注意点一:

  • 此处我们使用 update 标签来实现建表操作!

问题:

  • 当前是将每个建表语句都单独的列为一个 update 标签,并且对应一个 Java 方法
  • 此处我们能否改成一个 update 标签中包含多个建表语句,同时借助一个 Java 方法,完成上述多个表的创建呢?

回答:

  • MyBatis 支持一个 标签 中包含多个 sql 语句,其前提为,搭配 MySQL 或 Oracle 使用
  • 对于 SQLite 来说,是无法做到上述功能的
  • 即 当一个 update 标签中,写了多个 create table 语句时,只有第一个语句能执行

注意点二:

  • Exchange 和 Queue 这两个表,由于使用 name 做为主键,直接按照 name 进行删除即可
  • 但对于 Binding 来说,此时没有主键,其删除操作是针对 exchangeName 和 queueName 两个纬度进行筛选

问题:

  • 如果实现把 argument 键值对 与 数据库中的字符串类型相互转换呢?

回答:

  • 关键要点在于,MyBatis 在完成数据库操作时,会自动的调用到对象的 getter 和 setter
  • 比如 MyBatis 往数据库中写数据时,就会调用对象的 getter 方法,拿到属性值再往数据库中写
  • 如果这个过程中,让 getArgument 得到的结果为 String 类型,此时,就可以直接把这个数据写到数据库了

  • 比如 MyBatis 从数据库读数据时,就会调用对象的 setter 方法,将数据库中读到的结果设置到对象的属性中
  • 如果这个过程中,让 setArgument 的参数为 String 类型,并且在 setArguments 内部针对字符串解析,解析成一个 Map 对象
  • 综上,我们直接在包含 argument 成员变量的 Exchange 实体类 和  MSGQueue 实体类中,改写 argument 的 getter 和 setter 方法即可
//    这里的 getter setter 用于和数据库进行交互
    public String getArguments() {
//        是把当前的 arguments 参数,从 Map 转成 String (JSON)
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            return objectMapper.writeValueAsString(arguments);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
//        如果代码真异常了,返回一个空的 json 字符串就 ok
        return "{}";
    }

//    这个方法,是从数据库读数据之后,构造 Exchange 对象,会自动调用到
    public void setArguments(String argumentsJson) {
//        把参数中的 argumentsJson 按照 JSON 格式解析,转成上述的 Map 对象
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            this.arguments = objectMapper.readValue(argumentsJson, new TypeReference<HashMap<String,Object>>() {});
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }

注意:

  • 此处的第二个参数用来描述当前 JSON 字符串,要转成的 Java 对象是啥类型的
  • 如果是个简单类型,直接使用对应类型的类对象即可
  • 如果是集合类这样的复杂类型,可以使用 TypeReference 匿名内部类对象,来描述复杂类型的具体信息(通过泛型参数来描述的)

封装数据库操作

  • 此处我们将专门写一个类来整合上述的数据库操作
import com.example.demo.DemoApplication;
import com.example.demo.mqserver.core.Binding;
import com.example.demo.mqserver.core.Exchange;
import com.example.demo.mqserver.core.ExchangeType;
import com.example.demo.mqserver.core.MSGQueue;
import com.example.demo.mqserver.mapper.MetaMapper;

import java.io.File;
import java.util.List;

/*
* 通过这个类,来整合上述的数据库操作
* */
public class DataBaseManager {
//    从 Spring 中拿到现成的对象
    private MetaMapper metaMapper;

//    针对数据库进行初始化
    public void init() {
//        手动的获取到 MetaMapper
        metaMapper = DemoApplication.context.getBean(MetaMapper.class);

        if (!checkDBExists()) {
//            如果数据库不存在,就进行建库建表操作
//            先创建一个 data 目录
            File dataDir = new File("./data");
            dataDir.mkdir();
//            创建数据库
            createTable();
//            插入默认数据
            createDefaultData();
            System.out.println("[DataBaseManager] 数据库初始化完成!");
        }else {
//            数据库已经存在了,啥都不做即可
            System.out.println("[DataBaseManager] 数据库已经存在!");
        }
    }

    public void deleteDB() {
        File file = new File("./data/meta.db");
        boolean ret = file.delete();
        if(ret) {
            System.out.println("[DataBaseManager] 删除数据库文件成功!");
        }else {
            System.out.println("[DataBaseManager] 删除数据库文件失败!");
        }

        File dataDir = new File("./data");
//        使用 delete 删除目录的时候,需要保证目录是空的
        ret = dataDir.delete();
        if(ret) {
            System.out.println("[DataBaseManager] 删除数据库目录成功!");
        }else {
            System.out.println("[DataBaseManager] 删除数据库目录失败!");
        }
    }

    private boolean checkDBExists() {
        File file = new File("./data/meta.db");
        if(file.exists()) {
            return true;
        }
        return false;
    }

//    这个方法用来建表
//    建库操作并不需要手动执行(不需要手动创建 meta.db 文件)
//    首次执行这里的数据库操作的时候,就会自动的创建出 meta.db 文件来(MyBatis 帮我们完成的)
    private void createTable() {
        metaMapper.createExchangeTable();
        metaMapper.createQueueTable();
        metaMapper.createBindingTable();
        System.out.println("[DataBaseManager] 创建表完成!");
    }

//   给数据库表中,添加默认的数据
//   此处主要是添加一个默认的交换机
//   RabbitMQ 里有一个这样的设定: 带有一个 匿名 的交换机,类型是 DIRECT
    private void createDefaultData() {
//        构造一个默认的交换机
        Exchange exchange = new Exchange();
        exchange.setName("");
        exchange.setType(ExchangeType.DIRECT);
        exchange.setDurable(true);
        exchange.setAutoDelete(false);
        metaMapper.insertExchange(exchange);
        System.out.println("[DataBaseManager] 创建初始数据成功!");
    }

//    把其他的数据库操作,也在这个类中封装一下
    public void insertExchange(Exchange exchange) {
        metaMapper.insertExchange(exchange);
    }

    public List<Exchange> selectAllExchanges() {
       return metaMapper.selectAllExchanges();
    }

    public void deleteExchange(String exchangeName) {
        metaMapper.deleteExchange(exchangeName);
    }

    public void insertQueue(MSGQueue queue) {
        metaMapper.insertQueue(queue);
    }

    public List<MSGQueue> selectAllQueues() {
        return metaMapper.selectAllQueues();
    }

    public void deleteQueue(String queueName) {
        metaMapper.deleteQueue(queueName);
    }

    public void insertBinding(Binding binding) {
        metaMapper.insertBinding(binding);
    }

    public List<Binding> selectAllBindings (){
        return metaMapper.selectAllBindings();
    }

    public void deleteBinding(Binding binding) {
        metaMapper.deleteBinding(binding);
    }
}

注意点一: 

  • 谈到初始化,我们一般都会用到 构造方法
  • 但是此处的 init() 为一个普通方法
  • 构造方法一般是用来初始化类的属性,即一般不太会涉及到太多的业务逻辑
  • 但是此处的初始化是带有业务逻辑的,还是单独拎出来,手动来调用比较合适一些

注意点二:

  • 数据库初始化 =  建库建表 + 插入一些默认数据
  • 此处我们期望在 broker server 启动时,做出下列逻辑判定:
  1. 如果数据库已经存在了,即表啥的都有了,则不做任何操作
  2. 如果数据库不存在,则创建库,创建表,构造默认数据

实例理解

  • 例如,现在将 broker server 部署到一个新的服务器上
  • 显然,此时是没有数据库的,需让 broker server 启动时,自动将对应的数据库创建好
  • 但是如果是一个已经部署过的机器,当 broker server 重启时,就会发现数据库已经有了,此时将不做任何数据库相关操作
  • 综上,判定数据库是否存在,就等同于判定 meta.db 这个文件是否存在即可!

注意点三:

  • 当然手动获取 MetaMapper 的对象前提是改写启动类代码

针对 DataBaseManager 单元测试

  • 在设计单元测试时,要求单元测试用例 与 用例之间需相互独立,互不干扰

实例理解

  • 测试用例A => 测试过程中,给数据库里插入了一些数据
  • 测试用例B => 再针对 B 进行测试,可能 A 这里的数据,就会对 B 造成干扰
  • 测试用例C => 再针对 C 测试,A 和 B 都可能因为数据库的数据影响到 C

注意:

  • 此处的影响不一定是数据库,也可能是其他方面,如是否搞了个文件,是否占用了端口 等

解决方案:

  • 每个用例执行之前,先执行一段逻辑,搭建测试环境,准备好测试需要用到的一些东西
  • 每个用例执行之后,再执行一段逻辑,把用例执行过程中产生的中间结果,一些影响,给消除掉
  • 综上,我们可以使用 @BeforeEach 和 @AfterEach 这两个注解
import com.example.demo.mqserver.core.Binding;
import com.example.demo.mqserver.core.Exchange;
import com.example.demo.mqserver.core.ExchangeType;
import com.example.demo.mqserver.core.MSGQueue;
import com.example.demo.mqserver.datacenter.DataBaseManager;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

//加上这个注解之后,该类就会被识别为单元测试类
@SpringBootTest
public class DataBaseManagerTests {
    private DataBaseManager dataBaseManager = new DataBaseManager();

//    接下来下面这里需要编写多个 方法,每个方法都是一个/一组单元测试用例
//    还需要做一个准备工作,需要写两个方法,分别用于 "准备工作" 和 "收尾工作"

//    使用这个方法,来执行准备工作,每个用例执行前,都要调用这个方法\
    @BeforeEach
    public void setUp() {
//        由于在 init 中,需要通过 context 对象拿到 metaMapper 实例的
//        所以就需要先把 context 对象给搞出来
        DemoApplication.context = SpringApplication.run(DemoApplication.class);
        dataBaseManager.init();
    }

//    使用这个方法,来执行收尾工作,每个用例执行后,都要调用这个方法
    @AfterEach
    public void tearDown() {
//        这里要进行的操作,就是把数据库给清空(把数据库文件,meta.db 直接删了就行)
//        注意,此处不能直接就删除,而需要先关闭上述 context 对象!
//        此处的 context 对象,持有了 Meta 的实例, MetaMapper 实例又打开了 meta.db 数据库文件
//        如果 Meta.db 被别人打开了,此时的删除文件操作是不会成功的(Windows 系统的限制,Linux 则没有这个问题)
//        另一方面,获取 context 操作,会占用 8080 端口,此处的 close 也是释放 8080
        DemoApplication.context.close();
        dataBaseManager.deleteDB();
    }

    @Test
    public void testInitTable() {
//        由于 init 方法,已经在上面 setUp 中掉用过了,直接在测试用例代码中,检查当前的数据库状态即可
//        直接从数据库中查询,看数据是否符合预期
//        查交换机表,里面应该有一个数据(匿名的 exchange); 查队列,没有数据; 查绑定表,没有数据;
        List<Exchange> exchangeList = dataBaseManager.selectAllExchanges();
        List<MSGQueue> queueList = dataBaseManager.selectAllQueues();
        List<Binding> bindingList = dataBaseManager.selectAllBindings();

//        直接打印结果,通过肉眼,固然也可以,但是不优雅,不方便
//        更好的办法是使用断言
//        System.out.println(exchangeList.size());
//        assertEquals 判定结果是不是相等
//        注意这俩参数的顺序,虽然比较相等,谁在前谁在后,无所谓
//        但是 assertEquals 的形参,第一个形参叫做 expected(预期的),第二个形参叫做 actual(实际的)
        Assertions.assertEquals(1,exchangeList.size());
        Assertions.assertEquals("",exchangeList.get(0).getName());
        Assertions.assertEquals(ExchangeType.DIRECT,exchangeList.get(0).getType());
        Assertions.assertEquals(0,queueList.size());
        Assertions.assertEquals(0,bindingList.size());
    }

    private Exchange createTestExchange(String exchangeName) {
        Exchange exchange = new Exchange();
        exchange.setName(exchangeName);
        exchange.setType(ExchangeType.FANOUT);
        exchange.setAutoDelete(false);
        exchange.setDurable(true);
        exchange.setArguments("aaa",1);
        exchange.setArguments("bbb",2);
        return exchange;
    }

    @Test
    public void testInsertExchange() {
//        构造一个 Exchange 对象,插入到数据库中,再查询出来,看结果是否符合预期
        Exchange exchange = createTestExchange("testExchange");
        dataBaseManager.insertExchange(exchange);
//        插入完毕之后,查询结果
        List<Exchange> exchangeList = dataBaseManager.selectAllExchanges();
        Assertions.assertEquals(2,exchangeList.size());
        Exchange newExchange = exchangeList.get(1);
        Assertions.assertEquals("testExchange",newExchange.getName());
        Assertions.assertEquals(ExchangeType.FANOUT,newExchange.getType());
        Assertions.assertEquals(false,newExchange.isAutoDelete());
        Assertions.assertEquals(true,newExchange.isDurable());
        Assertions.assertEquals(1,newExchange.getArguments("aaa"));
        Assertions.assertEquals(2,newExchange.getArguments("bbb"));
    }

    @Test
    public void testDeleteExchange() {
//        先构造一个交换机,插入数据库,然后再按照名字删除即可!
        Exchange exchange = createTestExchange("testExchange");
        dataBaseManager.insertExchange(exchange);
        List<Exchange> exchangeList = dataBaseManager.selectAllExchanges();
        Assertions.assertEquals(2,exchangeList.size());
        Assertions.assertEquals("testExchange",exchangeList.get(1).getName());

//        进行删除操作
        dataBaseManager.deleteExchange("testExchange");
//        再次查询
        exchangeList = dataBaseManager.selectAllExchanges();
        Assertions.assertEquals(1,exchangeList.size());
        Assertions.assertEquals("",exchangeList.get(0).getName());
    }

    private MSGQueue createTestQueue(String queueName) {
        MSGQueue queue = new MSGQueue();
        queue.setName(queueName);
        queue.setDurable(true);
        queue.setAutoDelete(false);
        queue.setExclusive(false);
        queue.setArguments("aaa",1);
        queue.setArguments("bbb",2);
        return queue;
    }

    @Test
    public void testInsertQueue() {
        MSGQueue queue = createTestQueue("testQueue");
        dataBaseManager.insertQueue(queue);

        List<MSGQueue> queueList = dataBaseManager.selectAllQueues();

        Assertions.assertEquals(1,queueList.size());
        MSGQueue newQueue = queueList.get(0);
        Assertions.assertEquals("testQueue",newQueue.getName());
        Assertions.assertEquals(true,newQueue.isDurable());
        Assertions.assertEquals(false,newQueue.isAutoDelete());
        Assertions.assertEquals(false,newQueue.isAutoDelete());
        Assertions.assertEquals(1,newQueue.getArguments("aaa"));
        Assertions.assertEquals(2,newQueue.getArguments("bbb"));
    }

    @Test
    public void testDeleteQueue() {
        MSGQueue queue = createTestQueue("testQueue");
        dataBaseManager.insertQueue(queue);
        List<MSGQueue> queueList = dataBaseManager.selectAllQueues();
        Assertions.assertEquals(1,queueList.size());
//        进行删除
        dataBaseManager.deleteQueue("testQueue");
        queueList = dataBaseManager.selectAllQueues();
        Assertions.assertEquals(0,queueList.size());
    }

    private 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.selectAllBindings();
        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.selectAllBindings();
        Assertions.assertEquals(1,bindingList.size());

//        删除
        Binding toDeleteBinding = createTestBinding("testExchange","testQueue");
        dataBaseManager.deleteBinding(toDeleteBinding);
        bindingList = dataBaseManager.selectAllBindings();
        Assertions.assertEquals(0,bindingList.size());
    }
}

注意点一:

  • 相比于功能/业务代码,测试用例代码,编写起来是比较无聊的
  • 但是重要性是非常大的!
  • 这些操作会大大提高整个项目的开发效率!
  • 写代码,不太可能没有 bug,进行周密的测试,是应对 bug 的最有效手段

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/385841.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

腾讯云4核8G服务器够用吗?能支持多少人?

腾讯云4核8G服务器支持多少人在线访问&#xff1f;支持25人同时访问。实际上程序效率不同支持人数在线人数不同&#xff0c;公网带宽也是影响4核8G服务器并发数的一大因素&#xff0c;假设公网带宽太小&#xff0c;流量直接卡在入口&#xff0c;4核8G配置的CPU内存也会造成计算…

webpack面试解析

参考&#xff1a; 上一篇webpack相关的系列&#xff1a;webpack深入学习&#xff0c;搭建和优化react项目 爪哇教育字节面试官解析webpack-路白 1、Webpack中的module是什么&#xff1f; 通常来讲&#xff0c;一个 module 模块就是指一个文件中导出的内容&#xff0c;webpack…

全国计算机等级考试二级,MySQL数据库考试大纲(2023年版)

基本要求&#xff1a; 1.掌握数据库的基本概念和方法。 2.熟练掌握MySQL的安装与配置。 3.熟练掌握MySQL平台下使用&#xff33;&#xff31;&#xff2c;语言实现数据库的交互操作。 4.熟练掌握 MySQL的数据库编程。 5.熟悉 PHP 应用开发语言&#xff0c;初步具备利用该语言进…

Vue-自定义属性和插槽(五)

目录 自定义指令 基本语法 (全局&局部注册) 指令的值 练习&#xff1a;v-loading 指令封装 总结&#xff1a; 插槽&#xff08;slot&#xff09; 默认插槽 插槽 - 后备内容&#xff08;默认值&#xff09; 具名插槽 具名插槽基本语法: 具名插槽简化语法: 作…

RocksDB:高性能键值存储引擎初探

在现代的分布式系统和大数据应用中&#xff0c;一个高效、可靠的存储引擎是不可或缺的。RocksDB&#xff0c;由Facebook于2012年开发并随后开源&#xff0c;正是为了满足这类需求而诞生的。它是一个持久化的键值存储系统&#xff0c;特别适合在闪存&#xff08;Flash&#xff0…

AtCoder Beginner Contest 340 C - Divide and Divide【打表推公式】

原题链接&#xff1a;https://atcoder.jp/contests/abc340/tasks/abc340_c Time Limit: 2 sec / Memory Limit: 1024 MB Score: 300 points 问题陈述 黑板上写着一个整数 N。 高桥将重复下面的一系列操作&#xff0c;直到所有不小于2的整数都从黑板上移除&#xff1a; 选择…

SCI论文作图规范

SCI论文作图规范包括以下几个方面&#xff1a; 一、图片格式 SCI论文通常接受的图片格式包括TIFF、EPS和PDF等。其中&#xff0c;TIFF格式是一种高质量的图像格式&#xff0c;适用于需要高分辨率和颜色准确性的图片&#xff1b;EPS格式是一种矢量图形格式&#xff0c;适用于需…

Leetcode 1035 不相交的线

题意理解&#xff1a; 在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。 现在&#xff0c;可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线&#xff0c;这些直线需要同时满足满足&#xff1a; nums1[i] nums2[j]且绘制的直线不与任何其他连线&#xff…

Spring 事务

Spring 事务传播&#xff08;Propagation&#xff09;特性 REQUIRED 支持一个当前的事务&#xff0c;如果不存在创建一个新的。SUPPORTS 支持一个当前事务&#xff0c;如果不存在以非事务执行。MANDATORY 支持一个当前事务&#xff0c;如果不存在任何抛出异常。REQUIRES_NEW 创…

百面嵌入式专栏(面试题)驱动开发面试题汇总 2.0

沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇我们将介绍驱动开发面试题 。 1、Linux系统的组成部分? Linux内核、Linux文件系统、Linux shell、Linux应用程序。 2、Linux内核的组成部分? (1)第一种分类方式:内存管理子系统、进程管理子系统、文件管理子系…

react【六】 React-Router

文章目录 1、Router1.1 路由1.2 认识React-Router1.3 Link和NavLink1.4 Navigate1.5 Not Found页面配置1.6 路由的嵌套1.7 手动路由的跳转1.7.1 在函数式组件中使用hook1.7.2 在类组件中封装高阶组件 1.8 动态路由传递参数1.9 路由的配置文件以及懒加载 1、Router 1.1 路由 1.…

VC++ 绘制折线学习

win32 有三个绘制折线的函数&#xff1b; Polyline&#xff0c;根据给定点数组绘制折线&#xff1b; PolylineTo&#xff0c;除了绘制也更新当前位置&#xff1b; PolyPolyline&#xff0c;绘制多条折线&#xff0c;第一个参数是点数组&#xff0c;第二个参数是一个数组、指…

论文阅读-One for All : 动态多租户边缘云平台的统一工作负载预测

论文名称&#xff1a;One for All: Unified Workload Prediction for Dynamic Multi-tenant Edge Cloud Platforms 摘要 多租户边缘云平台中的工作负载预测对于高效的应用部署和资源供给至关重要。然而&#xff0c;在多租户边缘云平台中&#xff0c;异构的应用模式、可变的基…

HTTP的基本格式

HTTP的基本格式 .HTTPhttp的协议格式HTTPhttp的协议格式 . HTTP 应用层,一方面是需要自定义协议,一方面也会用到一些现成的协议. HTTP协议,就是最常用到的应用层协议. 使用浏览器,打开网站,使用手机app,加载数据,这些过程大概率都是HTTP来支持的 HTTP是一个超文本传输协议, 文…

家政小程序系统源码开发:引领智能生活新篇章

随着科技的飞速发展&#xff0c;小程序作为一种便捷的应用形态&#xff0c;已经深入到我们生活的方方面面。尤其在家庭服务领域&#xff0c;家政小程序的出现为人们带来了前所未有的便利。它不仅简化了家政服务的流程&#xff0c;提升了服务质量&#xff0c;还为家政服务行业注…

最新wordpress外贸主题

日用百货wordpress外贸主题 蓝色大气的wordpress外贸主题&#xff0c;适合做日用百货的外贸公司搭建跨境电商网站使用。 https://www.jianzhanpress.com/?p5248 添加剂wordpress外贸建站主题 橙色wordpress外贸建站主题&#xff0c;适合做食品添加剂或化工添加剂的外贸公司…

pm2启动的node项目访问不了,npm start却可以访问

netstat -ntlp输入该命令&#xff0c;查看启动的服务端口是否有被监听到&#xff0c;如3001&#xff0c;4000之类的&#xff0c;是node项目启动时候自己配的那个&#xff0c; 若没有&#xff0c;则执行 pm2 delete [app-id/app-name] 先删除启动的这个项目 例如pm2 delete my…

【电路笔记】-并联电感

并联电感 文章目录 并联电感1、概述2、并联电感示例13、互耦并联电感器4、并联电感示例25、并联电感示例36、总结当电感器的两个端子分别连接到另一个或多个电感器的每个端子时,电感器被称为并联连接在一起。 1、概述 所有并联电感器上的压降将是相同的。 然后,并联的电感器…

Elasticsearch:适用于 iOS 和 Android 本机应用程序的 Elastic APM

作者&#xff1a;来自 Elastic Akhilesh Pokhariyal, Cesar Munoz, Bryce Buchanan 适用于本机应用程序的 Elastic APM 提供传出 HTTP 请求和视图加载的自动检测&#xff0c;捕获自定义事件、错误和崩溃&#xff0c;并包括用于数据分析和故障排除目的的预构建仪表板。 适用于 …

【go语言】一个简单HTTP服务的例子

一、Go语言安装 Go语言&#xff08;又称Golang&#xff09;的安装过程相对简单&#xff0c;下面是在不同操作系统上安装Go语言的步骤&#xff1a; 在Windows上安装Go语言&#xff1a; 访问Go语言的官方网站&#xff08;golang.org&#xff09;或者使用国内镜像站点&#xff0…