RabbitMQ安装配置,封装工具类,发送消息及监听

1. Get-Started

docker安装rabbitmq

  1. 拉取镜像
[root@heima ~]# docker pull rabbitmq:3.8-management
3.8-management: Pulling from library/rabbitmq
7b1a6ab2e44d: Pull complete 
37f453d83d8f: Pull complete 
e64e769bc4fd: Pull complete 
c288a913222f: Pull complete 
13adc5da62c6: Pull complete 
bd67e639afcb: Pull complete 
9a48b5ad2519: Pull complete 
1cdfc59624be: Pull complete 
8f5ad79f0ad6: Pull complete 
Digest: sha256:543f7268600a27a39e2fdd532f8df479636fc0cf528aadde88d5fe718bed71e4
Status: Downloaded newer image for rabbitmq:3.8-management
docker.io/library/rabbitmq:3.8-management
  1. 创建目录
mkdir -p /home/apps/rabbitmq/data
  1. 运行容器
docker run \
-d \
--name rabbitmq \
--restart=always \
--privileged=true \
-p 5672:5672 \
-p 15672:15672 \
-v /home/apps/rabbitmq/data:/var/lib/rabbitmq \
-e RABBITMQ_DEFAULT_VHOST=vhost0 \
-e RABBITMQ_DEFAULT_USER=admin \
-e RABBITMQ_DEFAULT_PASS=admin123 \
rabbitmq:3.8-management
  1. 启用web界面管理插件
docker exec -it rabbitmq rabbitmq-plugins enable rabbitmq_management
或
-- 进入容器
docker exec -it rabbitmq /bin/bash
-- 安装插件
rabbitmq-plugins enable rabbitmq_management
  1. 浏览器访问 http://虚拟机ip:15672/出现以下界面说明安装成功。

在这里插入图片描述
输入上面在初始化Rabbitmq容器时我们自己指定了默认账号和密码:admin/admin123,如果没有指定的话那么rabbitmq的默认账号密码是:guest/guest

扩展安装延迟队列

  • 去官网下载插件(v3.8.17)
  • 地址:https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases
# 将下载好的插件复制到mq容器内部
docker cp rabbitmq_delayed_message_exchange-3.8.17.8f537ac.ez rabbitmq:/plugins# 进入mq容器
docker exec -it rabbitmq /bin/bash# 开启插件支持 
rabbitmq-plugins enable rabbitmq_delayed_message_exchange# 查看插件列表
rabbitmq-plugins list

2. 生产者模块

2.1 项目引入依赖:

rabbitmq依赖包,使用RabbitMq这2个依赖就够了。

<dependency><groupId>org.springframework.amqp</groupId><artifactId>spring-amqp</artifactId><scope>provided</scope>
</dependency>
<dependency><groupId>org.springframework.amqp</groupId><artifactId>spring-rabbit</artifactId><scope>provided</scope>
</dependency>

封装工具类用到的包

<!--hutool工具包-->
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.17</version>
</dependency>

2.2 工具类

  • RabbitMqHelper
import cn.hutool.core.lang.UUID;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
@Slf4j
public class RabbitMqHelper {public static final String REQUEST_ID_HEADER = "requestId";private final RabbitTemplate rabbitTemplate;private final MessagePostProcessor processor = new BasicIdMessageProcessor();private final ThreadPoolTaskExecutor executor;public RabbitMqHelper(RabbitTemplate rabbitTemplate) {this.rabbitTemplate = rabbitTemplate;executor = new ThreadPoolTaskExecutor();//配置核心线程数executor.setCorePoolSize(10);//配置最大线程数executor.setMaxPoolSize(15);//配置队列大小executor.setQueueCapacity(99999);//配置线程池中的线程的名称前缀executor.setThreadNamePrefix("mq-async-send-handler");// 设置拒绝策略:当pool已经达到max size的时候,如何处理新任务// CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());//执行初始化executor.initialize();}/*** 根据exchange和routingKey发送消息*/public <T> void send(String exchange, String routingKey, T t) {log.debug("准备发送消息,exchange:{}, RoutingKey:{}, message:{}", exchange, routingKey, t);// 1.设置消息标示,用于消息确认,消息发送失败直接抛出异常,交给调用者处理String id = UUID.randomUUID().toString(true);CorrelationData correlationData = new CorrelationData(id);// 2.设置发送超时时间为500毫秒rabbitTemplate.setReplyTimeout(500);// 3.发送消息,同时设置消息idrabbitTemplate.convertAndSend(exchange, routingKey, t, processor, correlationData);}/*** 根据exchange和routingKey发送消息,并且可以设置延迟时间*/public <T> void sendDelayMessage(String exchange, String routingKey, T t, Duration delay) {// 1.设置消息标示,用于消息确认,消息发送失败直接抛出异常,交给调用者处理String id = UUID.randomUUID().toString(true);CorrelationData correlationData = new CorrelationData(id);// 2.设置发送超时时间为500毫秒rabbitTemplate.setReplyTimeout(500);// 3.发送消息,同时设置消息idrabbitTemplate.convertAndSend(exchange, routingKey, t, new DelayedMessageProcessor(delay), correlationData);}/*** 根据exchange和routingKey 异步发送消息,并指定一个延迟时间** @param exchange   交换机* @param routingKey 路由KEY* @param t          数据* @param <T>        数据类型*/public <T> void sendAsync(String exchange, String routingKey, T t, Long time) {String requestId = MDC.get(REQUEST_ID_HEADER);CompletableFuture.runAsync(() -> {try {MDC.put(REQUEST_ID_HEADER, requestId);// 发送延迟消息if (time != null && time > 0) {sendDelayMessage(exchange, routingKey, t, Duration.ofMillis(time));} else {send(exchange, routingKey, t);}} catch (Exception e) {log.error("推送消息异常,t:{},", t, e);}}, executor);}/*** 根据exchange和routingKey 异步发送消息** @param exchange   交换机* @param routingKey 路由KEY* @param t          数据* @param <T>        数据类型*/public <T> void sendAsync(String exchange, String routingKey, T t) {sendAsync(exchange, routingKey, t, null);}
}
  • MqConfig
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.MDC;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.config.ContainerCustomizer;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.retry.MessageRecoverer;
import org.springframework.amqp.rabbit.retry.RepublishMessageRecoverer;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;@Configuration
@ConditionalOnClass(value = {MessageConverter.class, AmqpTemplate.class})
public class MqConfig implements EnvironmentAware{public static final String REQUEST_ID_HEADER = "requestId";public static final String ERROR_EXCHANGE = "error.topic";public static final String ERROR_KEY_PREFIX = "error.";public static final String ERROR_QUEUE_TEMPLATE = "error.{}.queue";private String defaultErrorRoutingKey;private String defaultErrorQueue;@Bean(name = "rabbitListenerContainerFactory")@ConditionalOnProperty(prefix = "spring.rabbitmq.listener", name = "type", havingValue = "simple",matchIfMissing = true)SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory(SimpleRabbitListenerContainerFactoryConfigurer configurer, ConnectionFactory connectionFactory,ObjectProvider<ContainerCustomizer<SimpleMessageListenerContainer>> simpleContainerCustomizer) {SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();configurer.configure(factory, connectionFactory);simpleContainerCustomizer.ifUnique(factory::setContainerCustomizer);factory.setAfterReceivePostProcessors(message -> {Object header = message.getMessageProperties().getHeader(REQUEST_ID_HEADER);if(header != null) {MDC.put(REQUEST_ID_HEADER, header.toString());}return message;});return factory;}@Beanpublic MessageConverter messageConverter(ObjectMapper mapper){// 1.定义消息转换器Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter(mapper);// 2.配置自动创建消息id,用于识别不同消息jackson2JsonMessageConverter.setCreateMessageIds(true);return jackson2JsonMessageConverter;}/*** <h1>消息处理失败的重试策略</h1>* 本地重试失败后,消息投递到专门的失败交换机和失败消息队列:error.queue*/@Bean@ConditionalOnClass(MessageRecoverer.class)@ConditionalOnMissingBeanpublic MessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate){// 消息处理失败后,发送到错误交换机:error.direct,RoutingKey默认是error.微服务名称return new RepublishMessageRecoverer(rabbitTemplate, ERROR_EXCHANGE, defaultErrorRoutingKey);}/*** rabbitmq发送工具**/@Bean@ConditionalOnMissingBean@ConditionalOnClass(RabbitTemplate.class)public RabbitMqHelper rabbitMqHelper(RabbitTemplate rabbitTemplate){return new RabbitMqHelper(rabbitTemplate);}/*** 专门接收处理失败的消息*/@Beanpublic DirectExchange errorMessageExchange(){return new DirectExchange(ERROR_EXCHANGE);}@Beanpublic Queue errorQueue(){return new Queue(defaultErrorQueue, true);}@Beanpublic Binding errorBinding(Queue errorQueue, DirectExchange errorMessageExchange){return BindingBuilder.bind(errorQueue).to(errorMessageExchange).with(defaultErrorRoutingKey);}@Overridepublic void setEnvironment(Environment environment) {String appName = environment.getProperty("spring.application.name");this.defaultErrorRoutingKey = ERROR_KEY_PREFIX + appName;this.defaultErrorQueue = StrUtil.format(ERROR_QUEUE_TEMPLATE, appName);}
}
  • Processor
import cn.hutool.core.lang.UUID;
import org.slf4j.MDC;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;public class BasicIdMessageProcessor implements MessagePostProcessor {public static final String REQUEST_ID_HEADER = "requestId";@Overridepublic Message postProcessMessage(Message message) throws AmqpException {String requestId = MDC.get(REQUEST_ID_HEADER);if (requestId == null) {requestId = UUID.randomUUID().toString(true);}// 写入RequestID标示message.getMessageProperties().setHeader(REQUEST_ID_HEADER, requestId);return message;}
}----------------------------------------------------------------------import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import java.time.Duration;public class DelayedMessageProcessor extends BasicIdMessageProcessor {private final long delay;public DelayedMessageProcessor(Duration delay) {this.delay = delay.toMillis();}@Overridepublic Message postProcessMessage(Message message) throws AmqpException {// 1.添加消息idsuper.postProcessMessage(message);// 2.添加延迟时间message.getMessageProperties().setHeader("x-delay", delay);return message;}
}

2.3 配置

通常mq是放在common模块中,别的模块需要mq时就引入该模块,因此需要把MqConfig放在IOC容器里加载。

  • resources/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.tianji.common.autoconfigure.mq.MqConfig

配置rabbitmq的地址等信息

  • boostrap.yml
spring:rabbitmq:host: ${mydemo.mq.host:192.168.150.101}port: ${mydemo.mq.port:5672}virtual-host: ${mydemo.mq.vhost:/vhost0}username: ${mydemo.mq.username:admin}password: ${mydemo.mq.password:admin123}listener:simple:retry:enabled: ${mydemo.mq.listener.retry.enable:true} # 开启消费者失败重试initial-interval: ${mydemo.mq.listener.retry.interval:1000ms} # 初始的失败等待时长为1秒multiplier: ${mydemo.mq.listener.retry.multiplier:2} # 失败的等待时长倍数,下次等待时长 = multiplier * last-intervalmax-attempts: ${mydemo.mq.listener.retry.max-attempts:3} # 最大重试次数stateless: ${mydemo.mq.listener.retry.stateless:true} # true无状态;false有状态。如果业务中包含事务,这里改为false

2.4 测试文件

TestController

@RestController
public class TestController {@AutowiredRabbitMqHelper rabbitMqHelper;@GetMapping("/hello")public String hello(){return "hello";}@GetMapping("/sendMsg")public String sendMsg(){rabbitMqHelper.send("order.topic", // "order.topic""order.pay", // "order.pay"UserDto.builder().id(10001).name("gz").age(23).phone("15500000001").email("123@qq.com").build());return "success";}
}

UserDto

import lombok.Builder;
import lombok.Data;@Data
@Builder
public class UserDto {private Integer id;private String name;private String phone;private Integer age;private String email;
}

2.5 生产者测试

生产者模块目录结构
在这里插入图片描述

启动生产者服务,访问http://127.0.0.1:8080/sendMsg
在这里插入图片描述报错了,原因是没有消费者消费消息
但是发现创建了新的错误消息exchange和queue
在这里插入图片描述
因此,接下来创建消费者监听消息。

3. 消费者

3.1 引入通用模块

由于消费者也需要使用刚刚的工具类和UserDto用来接收消息。
在这里插入图片描述
而这些工具类和UserDto都在模块rabbitmq-demo中。因此,在消费者模块中引入该生产者模块(如果这些通用的配置和实体类dto都在一个通用的模块common中,哪些模块需要发送消息,哪些模块需要监听消息,就都引入common模块就行了)

    <groupId>com.gzdemo</groupId><artifactId>rabbitmq-demo</artifactId><version>1.0-SNAPSHOT</version>

因为rabbitmq-demo模块中已经引入了RabbitMq相关依赖,因此消费者模块不需要重复引入RabbitMq相关的依赖。只需要把依赖包添加到classpath下,再import就行了
在这里插入图片描述
在这里插入图片描述

3.2 配置yml

与生产者的配置相同,通常只有消费者才需要配置listener,生产者不需要。
在这里插入图片描述

3.3 编写listener

import com.gzdemo.rabbitmq.pojos.UserDto;import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;@Component
public class UserListener {/*** 监听消息*/@RabbitListener(bindings = @QueueBinding(value = @Queue(value = "learning.lesson.pay.queue", durable = "true"),exchange = @Exchange(name = "order.topic", type = ExchangeTypes.TOPIC),key = "order.pay"))public void listenLessonPay(UserDto userDto){System.out.println(userDto);}
}

3.4 消费者目录结构

这里是随便找了个同工程中的子模块作为消费者测试,在UserListener类中监听消息
在这里插入图片描述

4 测试消息发送、监听

启动这两个服务,浏览器访问http://127.0.0.1:8080/sendMsg,多访问几次后发现消费者接收到了消息
在这里插入图片描述生产者console

2024-06-14 05:46:39.187  INFO 106168 --- [nio-8080-exec-1] o.s.a.r.c.CachingConnectionFactory       : Attempting to connect to: [192.168.150.101:5672]
2024-06-14 05:46:39.210  INFO 106168 --- [nio-8080-exec-1] o.s.a.r.c.CachingConnectionFactory       : Created new connection: rabbitConnectionFactory#399d82:0/SimpleConnection@1673b17 [delegate=amqp://admin@192.168.150.101:5672/vhost0, localPort= 61317]

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AI视频智能监管赋能城市管理:打造安全有序的城市环境

一、方案背景 随着城市化进程的加速和科技的飞速发展&#xff0c;街道治安问题日益凸显&#xff0c;治安监控成为维护社会稳定和保障人民安全的重要手段。当前&#xff0c;许多城市已经建立了较为完善的治安监控体系&#xff0c;但仍存在一些问题。例如&#xff0c;监控设备分…

ADS1220芯片写寄存器失败

1&#xff09;场景&#xff1a;最近调试ADS1220 的芯片&#xff0c;需要读取不同通道的AD值&#xff0c;修改寄存器0的值时一直失败 但是在单片机启动时&#xff0c;写寄存器0时&#xff0c;值能正确写入&#xff0c;并正确读出&#xff0c;之后写完读取出的都是FF或其他异常值…

新渠道+1!TDengine Cloud 入驻 Azure Marketplace

近日&#xff0c;TDengine Cloud 正式入驻微软云 Marketplace&#xff0c;为全球更多用户带来全托管的时序数据处理服务。这一举措也丰富了 TDengine 的订阅渠道&#xff0c;为用户提供了极大的便捷性。现在&#xff0c;您可以通过微软云 Marketplace 轻松订阅并部署 TDengine …

电影美学复古胶片特效视频转场模板 | Premiere Pro 项目工程文件

这个Premiere Pro项目工程文件是一个电影美学胶片特效视频转场模板&#xff0c;每个过渡效果都散发出一种有机的怀旧魅力&#xff0c;让人回忆起经典电影卷轴和模拟摄影的独特美感。 项目特点&#xff1a; 胶片烧伤过渡效果&#xff1a;包括从微妙的闪烁到大胆的爆发&#xff…

VMware虚拟机三种网络模式设置 - Bridged(桥接模式)

一、前言 由于linux目前很热门&#xff0c;越来越多的人在学习linux&#xff0c;但是买一台服务放家里来学习&#xff0c;实在是很浪费。那么如何解决这个问题&#xff1f;虚拟机软件是很好的选择&#xff0c;常用的虚拟机软件有vmware workstations和virtual box等。 在使用虚…

10 种语言文本准确渲染;Mac无需联网的本地聊天应用;多模态语言模型(MLM)基准测试的引擎;Yolo DotNet版本

✨ 1: Glyph-ByT5 10 种语言文本准确渲染&#xff0c;将文本渲染的准确性从提高到近 90% &#xff0c;同时还能实现段落渲染自动布局 Glyph-ByT5是一种定制的文本编码器&#xff0c;旨在实现准确的文字视觉渲染。其核心思想是通过细致的字形-文本配对数据集的微调&#xff0c…

JWT整合Gateway实现鉴权(RSA与公私密钥工具类)

一.业务流程 1.使用RSA生成公钥和私钥。私钥保存在授权中心&#xff0c;公钥保存在网关(gateway)和各个信任微服务中。 2.用户请求登录。 3.授权中心进行校验&#xff0c;通过后使用私钥对JWT进行签名加密。并将JWT返回给用户 4.用户携带JWT访问 5.gateway直接通过公钥解密JWT进…

最好用的智能猫砂盆存在吗?自用分享智能猫砂盆测评!

在现代都市的忙碌生活中&#xff0c;作为一名上班族&#xff0c;经常因为需要加班或频繁出差而忙碌得不可开交。急匆匆地出门&#xff0c;却忘了给猫咪及时铲屎。但是大家要知道&#xff0c;不及时清理猫砂盆会让猫咪感到不适&#xff0c;还会引发各种健康问题&#xff0c;如泌…

「6.18更新日志」JVS·智能BI、规则引擎功能更新说明

项目介绍 JVS是企业级数字化服务构建的基础脚手架&#xff0c;主要解决企业信息化项目交付难、实施效率低、开发成本高的问题&#xff0c;采用微服务配置化的方式&#xff0c;提供了 低代码数据分析物联网的核心能力产品&#xff0c;并构建了协同办公、企业常用的管理工具等&am…

从视频创意到传播策略 | 医药产品TVC新媒体传播方案

作为营销策划人&#xff0c;你一定在寻找能够激发创意灵感、拓展策划视野的实战案例。这份最新传播方案由Unithought精心打造&#xff0c;不仅是一份详尽的策划指南&#xff0c;更是一次深入患者心灵的品牌传播实践。 何策网&#xff0c;每日收录全网方案PPT &#xff01; 方…

Ubuntu server 24 (Linux) 安装客户端(windows/linux) Zabbix 7.0 LTS Zabbix agent2

一 Ubuntu(linux)安装客户端 1 Ubuntu 24 安装Zabbix agent2 #安装agent库 sudo wget https://repo.zabbix.com/zabbix/7.0/ubuntu/pool/main/z/zabbix-release/zabbix-release_7.0-1ubuntu24.04_all.deb sudo dpkg -i zabbix-release_7.0-1ubuntu24.04_all.deb sudo apt u…

Windows下MySQL数据库定期备份SQL文件与删除历史备份文件.bat脚本

目录 一、功能需求 二、解决方案 (1)新建文件夹及批处理文件 (2)编写备份脚本 ①完整脚本 ②参数修改 (3)编写定期删除备份脚本 ①根据文件名识别日期进行删除 ② 根据文件的修改日期删除 (4)设置定时器 (5)常见报错与处理 一、功能需求 在Windows系统下…

对比4090及4090D:国区“特供”与原版相比有何区别?

2023年12月28日 英伟达宣布正式发布GeForce RTX 4090D&#xff0c;对比于一年前上市的4090芯片&#xff0c;两者的区别与差异在哪&#xff1f;而在当前比较火热的大模型推理、AI绘画场景方面 两者各自的表现又如何呢&#xff1f; 规格与参数信息对比现在先来看看GeForce RT…

手把手教你创建并启动一个Vue3项目(Windows版)

一、Node安装 1、下载地址&#xff1a;Node.js — Run JavaScript Everywhere 2、安装Node&#xff0c;双击启动一直Next 3、验证安装Node是否成功&#xff0c;打开CMD命令窗口&#xff0c;输入node -v&#xff0c;显示版本就表示成功 4、验证安装npm是否成功&#xff0c;npm是…

数据库大作业——音乐平台数据库管理系统

W...Y的主页&#x1f60a; 代码仓库分享&#x1f495; 《数据库系统》课程设计 &#xff1a;流行音乐管理平台数据库系统&#xff08;本数据库大作业使用软件sql server、dreamweaver、power designer&#xff09; 目录 系统需求设计 数据库概念结构设计 实体分析 属性分…

Go源码--sync库(3):sync.Pool(2)

回收 回收其实就是将 pool.local 置为空 可以让垃圾回收器回收 我们来看下 源码 func init() {// 将 poolCleanup 注册到 gc开始前的准备工作处理器中在 STW时执行runtime_registerPoolCleanup(poolCleanup) }这里注册了清理程序到GC前准备工作 也就是发生GC前需要执行这段代…

【吊打面试官系列-Mysql面试题】Myql 中的事务回滚机制概述 ?

大家好&#xff0c;我是锋哥。今天分享关于 【Myql 中的事务回滚机制概述 ?】面试题&#xff0c;希望对大家有帮助&#xff1b; Myql 中的事务回滚机制概述 ? 事务是用户定义的一个数据库操作序列&#xff0c;这些操作要么全做要么全不做&#xff0c;是一个不可分割的工作单位…

flutter 打包 exe

采用官方的MSIX打包 原文链接 https://blog.csdn.net/weixin_44786530/article/details/135308360 第一步&#xff1a;安装依赖 在项目根目录&#xff0c;执行命令&#xff1a; flutter pub add --dev msix 等待安装完成&#xff0c;就好了 第二步&#xff1a;打包编译 当m…

数据库 | 试卷五试卷六试卷七

1. 主码不相同&#xff01;相同的话就不能唯一标识非主属性了 2.从关系规范化理论的角度讲&#xff0c;一个只满足 1NF 的关系可能存在的四方面问题 是&#xff1a; 数据冗余度大&#xff0c;插入异常&#xff0c;修改异常&#xff0c;删除异常 3.数据模型的三大要素是什么&…