springcloud集成seata实现分布式事务
Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。
官网:Apache Seata
文章目录
- 一、部署
- 1.下载
- 2.修改配置,nacos作注册中心,db存储
- 二、集成到springcloud项目
- 1.引入依赖
- 2.修改配置
- 3.新建数据表
- 4.编写代码
- 5.测试结果
一、部署
由于网络问题一直拉取docker镜像失败,所以这里采用了下载zip包直接部署的方式
版本说明 · alibaba/spring-cloud-alibaba Wiki · GitHub (需要和springcloud的版本对应)
1.下载
直接部署 | Apache Seata
上传服务器并解压

2.修改配置,nacos作注册中心,db存储
修改conf/application.yml
server:port: 7091spring:application:name: seata-serverlogging:config: classpath:logback-spring.xmlfile:path: ${user.home}/logs/seataextend:logstash-appender:destination: 192.168.100.52:4560kafka-appender:bootstrap-servers: 192.168.100.52:9092topic: logback_to_logstashconsole:user:username: seatapassword: seataseata:config:# support: nacos, consul, apollo, zk, etcd3type: nacosnacos:server-addr: 192.168.100.53:8848namespace: 17a4ea5e-f549-4e4a-97a4-52ee2a9f466cgroup: spmp-systemusername: nacospassword: nacosdata-id: seataServer.propertiesregistry:# support: nacos, eureka, redis, zk, consul, etcd3, sofatype: nacosnacos:application: seata-serverserver-addr: 192.168.100.53:8848group: spmp-systemnamespace: 17a4ea5e-f549-4e4a-97a4-52ee2a9f466c# tc集群名称cluster: defaultusername: nacospassword: nacos
# server:
# service-port: 8091 #If not configured, the default is '${server.port} + 1000'security:secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017tokenValidityInMilliseconds: 1800000ignore:urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
此时启动seata服务端,已经可以在nacos服务列表看到seata-server服务
cd bin
sh seata-seaver.sh

然后在nacos新建配置文件seataServer.properties

store.mode=db
store.db.dbType=mysql
store.db.datasource=druid
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://192.168.100.52:3306/seata?characterEncoding=UTF8&autoReconnect=true&serverTimezone=Asia/Shanghai
store.db.user=seata
store.db.password=seata
这里注意先建数据库seata,然后执行建表sql,脚本在script/server/db/下的mysql.sql

然后重启seata服务端
可以从seata启动日志 logs/start.out 看到读取配置的相关信息

二、集成到springcloud项目
这里我们拿项目里其中两个微服务来测试,如图所示,服务1是被调用方,服务2是调用方

1.引入依赖
两个微服务的pom文件里都需要引入seata依赖
<dependency><groupId>io.seata</groupId><artifactId>seata-spring-boot-starter</artifactId><version>1.6.1</version>
</dependency>
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId><version>2021.0.5.0</version><exclusions><exclusion><groupId>io.seata</groupId><artifactId>seata-spring-boot-starter</artifactId></exclusion></exclusions>
</dependency>
2.修改配置
修改两个微服务的配置文件,这里对应上前面seata服务端的配置
seata:registry:type: nacosnacos:application: seata-serverserver-addr: 192.168.100.53:8848group: spmp-systemnamespace: 17a4ea5e-f549-4e4a-97a4-52ee2a9f466cusername: nacospassword: nacosconfig:type: nacosnacos:server-addr: 192.168.100.53:8848group: spmp-systemnamespace: 17a4ea5e-f549-4e4a-97a4-52ee2a9f466cdataId: seataServer.propertiesusername: nacospassword: nacostx-service-group: spmp-system
3.新建数据表
两个服务都需要新建undo_log表,在事务回滚时需要用到,建表sql:
CREATE TABLE `undo_log` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`branch_id` bigint(20) NOT NULL,`xid` varchar(100) NOT NULL,`context` varchar(128) NOT NULL,`rollback_info` longblob NOT NULL,`log_status` int(11) NOT NULL,`log_created` datetime NOT NULL,`log_modified` datetime NOT NULL,`ext` varchar(100) DEFAULT NULL,PRIMARY KEY (`id`),UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
4.编写代码
-
修改全局异常处理器GlobalExceptionHandler
由于项目里的全局处理器通常都会将所有异常拦截,然后返回统一封装结果,而这会导致异常无法抛出
/*** 全局异常处理器** @author ruoyi*/ @RestControllerAdvice public class GlobalExceptionHandler {private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);/*** 先判断是否是seata全局事务异常,如果是,就直接抛给调用方,让调用方回滚事务* @param e* @throws Exception*/private void checkSeataError(Exception e) throws Exception {log.info("seata全局事务ID: {}", RootContext.getXID());// 如果是在一次全局事务里出异常了,就不要包装返回值,将异常抛给调用方,让调用方回滚事务if (StrUtil.isNotBlank(RootContext.getXID())) {throw e;}}/*** 请求方式不支持*/@ExceptionHandler(HttpRequestMethodNotSupportedException.class)public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, HttpServletRequest request) throws Exception {checkSeataError(e);String requestUri = request.getRequestURI();log.error("请求地址'{}',不支持'{}'请求", requestUri, e.getMethod());return AjaxResult.error(e.getMessage());}/*** 业务异常*/@ExceptionHandler(ServiceException.class)public AjaxResult handleServiceException(ServiceException e, HttpServletRequest request) throws Exception {checkSeataError(e);log.error(e.getMessage(), e);Integer code = e.getCode();return StringUtils.isNotNull(code) ? AjaxResult.error(code, StrUtil.isEmpty(e.getMessage()) ? e.getCause().getMessage() : e.getMessage()) : AjaxResult.error(StrUtil.isEmpty(e.getMessage()) ? e.getCause().getMessage() : e.getMessage());}/*** 请求参数类型不匹配*/@ExceptionHandler(MethodArgumentTypeMismatchException.class)public AjaxResult handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request) throws Exception {checkSeataError(e);String requestUri = request.getRequestURI();String value = Convert.toStr(e.getValue());if (StringUtils.isNotEmpty(value)) {value = EscapeUtil.clean(value);}log.error("请求参数类型不匹配'{}',发生系统异常.", requestUri, e);return AjaxResult.error(String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',但输入值为:'%s'", e.getName(), e.getRequiredType().getName(), value));}/*** 切面异常统一捕获*/@ExceptionHandler(AspectException.class)public ResponseResult<?> handleAspectException(AspectException aspectException) {aspectException.printStackTrace();return ResponseResult.error(aspectException.getResultStatus(), null);}/*** 系统基类异常捕获*/@ExceptionHandler(BasesException.class)public ResponseResult<?> handleBasesException(BasesException basesException) throws Exception {checkSeataError(basesException);basesException.printStackTrace();return ResponseResult.error(basesException.getResultStatus(), null);}/*** 拦截未知的运行时异常*/@ExceptionHandler(RuntimeException.class)public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request) throws Exception {checkSeataError(e);String requestUri = request.getRequestURI();log.error("请求地址'{}',发生未知异常.", requestUri, e);return AjaxResult.error(e.getMessage());}/*** 系统异常*/@ExceptionHandler(Exception.class)public AjaxResult handleException(Exception e, HttpServletRequest request) throws Exception {checkSeataError(e);String requestUri = request.getRequestURI();log.error("请求地址'{}',发生系统异常.", requestUri, e);return AjaxResult.error(e.getMessage());}/*** 自定义验证异常*/@ExceptionHandler(BindException.class)public AjaxResult handleBindException(BindException e) throws Exception {checkSeataError(e);log.error(e.getMessage(), e);String message = e.getAllErrors().get(0).getDefaultMessage();return AjaxResult.error(message);}/*** 自定义验证异常*/@ExceptionHandler(MethodArgumentNotValidException.class)public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) throws Exception {checkSeataError(e);log.error(e.getMessage(), e);String message = e.getBindingResult().getFieldError().getDefaultMessage();return ResponseResult.error(message);}/*** 内部认证异常*/@ExceptionHandler(InnerAuthException.class)public AjaxResult handleInnerAuthException(InnerAuthException e) throws Exception {checkSeataError(e);return AjaxResult.error(e.getMessage());}...... } -
修改Feign熔断降级方法
由于项目对远程调用接口还做了熔断降级操作,导致调用方仍然识别不到异常,所以这里将熔断降级方法修改下,让其能正常抛异常
@Component @Slf4j public class ConstructionProviderFallback implements IConstructionProvider {@Overridepublic ResponseResult<String> testSeata(Boolean error) {if (error) {throw new RuntimeException("降级方法中---模拟被调用方异常");}return ResponseResult.success("----------------testSeata接口远程调用熔断-----------------");} } -
启动类增加AOP注解
由于全局事务注解@GlobalTransactional底层是基于AOP实现,所以需要给两个服务的启动类都加上AOP注解
@EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true) -
调用方测试接口
/*** 测试全局事务* @return*/ @ApiOperation("测试全局事务") @GetMapping("/testSeata") @ApiImplicitParam(name = "type", value = "1:模拟调用方异常 其他:模拟被调用方异常") public ResponseResult<Boolean> testSeata(@RequestParam Integer type) {SecurityTest securityTest = new SecurityTest();securityTest.setTestColumn("测试全局事务");securityTest.setOrganizeId(1L);return ResponseResult.success(testSeataService.testSeata(type,securityTest)); }@GlobalTransactional @Override public Boolean testSeata(Integer type, SecurityTest securityTest) {log.info("seata全局事务ID: {}", RootContext.getXID());if (type!=null&&type==1) {//先远程调用construction服务保存远程服务数据constructionProvider.testSeata(false);//再保存自己服务数据securityTestService.save(securityTest);//模拟调用方异常throw new RuntimeException("模拟调用方异常");} else {//先保存自己服务数据securityTestService.save(securityTest);//再远程调用construction服务保存远程服务数据,且模拟被调用方异常constructionProvider.testSeata(true);}return true; }这里测试两种情况,调用方异常事务回滚,还有被调用方异常事务回滚
-
被调用方提供的Feign接口
@Service(value = "IConstructionProvider") @FeignClient(value = ConstructionProviderConstant.MATE_CLOUD_CONSTRUCTION, fallback = ConstructionProviderFallback.class) public interface IConstructionProvider {/*** 测试全局事务* @param error* @return*/@GetMapping(ConstructionProviderConstant.TEST_SEATA)ResponseResult<String> testSeata(@RequestParam("error") Boolean error);}这里当时遇到了一个坑 :
-
如果不写@RequestParam(“error”) ,会识别成POST请求,然后报错不支持POST请求
-
如果写了@RequestParam,但是没设置value属性,即写@RequestParam Boolean error,也会报错
参考:Feign 调用报 RequestParam.value() was empty on parameter 0-CSDN博客
实现:

正常调用:
/*** 测试Seata全局事务* @param error 是否模拟被调用方异常* @return*/ @Override @ApiOperation(value = "测试Seata全局事务", notes = "测试Seata全局事务", httpMethod = "GET") @GetMapping(ConstructionProviderConstant.TEST_SEATA) @SentinelResource(value = ConstructionProviderConstant.TEST_SEATA, fallbackClass = ConstructionProviderFallback.class, fallback = "testFeign") public ResponseResult<String> testSeata(@RequestParam(value = "error") Boolean error) {SecurityTest1 test = new SecurityTest1();test.setTestColumn("seata");test.setOrganizeId(1L);securityTestService.save(test);if (error) {throw new RuntimeException("模拟被调用方异常");}return ResponseResult.success("---------------testSeata接口正常------------------"); }熔断降级:
@Override public ResponseResult<String> testSeata(Boolean error) {if (error) {throw new RuntimeException("降级方法中---模拟被调用方异常");}return ResponseResult.success("----------------testSeata接口远程调用熔断-----------------"); } -
5.测试结果
分别测试了调用方异常、被调用方异常的情况,均能实现全局事务回滚(两边的数据库都回滚了),如下图所示


下面是seata控制台的信息(存于数据库里)

这里我测试的结果是 只有调用方和被调用方都有事务回滚 才会有信息,而且会定期清除
相关文章:
springcloud集成seata实现分布式事务
Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。 官网:Apache Seata 文章目录 一、部署1.下载2.修改配置,nacos作注册中心,db存储 二、集成到springcloud项目1.引入依赖2.修改…...
[Leetcode 61][Medium]-旋转链表
目录 一、题目描述 二、整体思路 三、代码 一、题目描述 原题链接 二、整体思路 首先发现这样的规律:当k大于等于链表中节点总数n时,会发现此时旋转后的链表和kk%n时的旋转后的链表一样。同时对于特殊情况n0和n1时,无论k的值为多少都可以…...
高效分页策略:掌握 LIMIT 语句的正确使用方法与最佳实践
本文主要介绍limit 分页的弊端及线上应该怎么用 LIMIT M,N 平时经常见到使用 <limit m,n> 合适的 order by 来实现分页查询,这样做到底性能如何呢? 先来简单分析下,然后再实际验证一下。 无索引条件下,需要做大量的文件排…...
拼图游戏02
文章目录 概要整体架构流程代码过程小结 概要 现在需要将图片添加界面中 关键点在于它如何动态地根据游戏状态更新用户界面。它使用了Swing的布局管理器来定位组件,并且通过ImageIcon和JLabel来显示图像。注意,路径字符串中的反斜杠在Java中是转义字符…...
在本地进行Django支付宝扫码支付-当面付开发
这几天涉及到一个个人项目的支付开发场景,正好完成之后,做一下开发记录,给有需要的朋友做一下参考 涉及安装Python环境请参考我专栏中的历史文章,这里不再重复说明 环境: Python3.11 使用Django框架 因本次代码为沙…...
redis-RedisTemplate.opsForGeo 的geo地理位置相关的方法演示
主要方法:add : 添加一个地理位置distance: 计算两个元素之间的距离hash: 获取元素经纬度坐标经过geohash算法生成的base32编码值position: 获取集合中任意元素的经纬度坐标,可以一次获取多个radius:查询某个坐标或某个成员&#…...
做短视频矩阵要十几人团队吗?云微客助阵,一人即可
现在市面上主流的新媒体平台都进军了短视频赛道,对于众多企业和个人来说,短视频矩阵更是成为了提升影响力和拓展业务的关键。企业或个人可以根据自身产品特点和目标用户群体,构建账号矩阵,在多平台上建立账号矩阵,还可…...
常用语音识别开源工具的对比与实践
常用语音识别开源工具的对比 一.工具概述 1. WeNet 设计目标:WeNet 的设计主要聚焦于端到端(E2E)语音识别,特别是在流式识别方面的优化。其目标是提供一个可以在实际应用中达到低延迟和高精度的系统。模型架构: Con…...
Fortify代码安全测试工具在静态应用安全测试(SAST)方面针对典型问题的改进
Fortify代码安全测试工具作为行业内资深的老牌软件安全测试工具,可以同时支持静态代码扫描和动态代码扫描,本文我们讲述的主要是在静态代码扫描领域Fortify所面临的问题,以及最新的改进。 在应用安全领域,特别是静态应用安全测试&…...
AWS 消息队列服务 SQS
AWS 消息队列服务 SQS 引言什么是 SQSSQS 访问策略 Access Policy示例:如何为 DataLake Subscription 配置 SQS 引言 应用系统需要处理海量数据,数据发送方和数据消费方是通过什么方式来无缝集成消费数据的,AWS 提供 SQS 消息队列服务来解决…...
【iOS】——响应者链和事件传递链
事件传递 事件传递流程 发生触摸事件后,系统会将该事件封装成UIEvent对象加入到一个由UIApplication管理的事件队列 UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口…...
mysql查询慢
可能是连接数,或者缓存不够,可尝试添加如下参数,重启mysql [mysqld] max_connections50000 interactive_timeout604800 lock_wait_timeout600 wait_timeout604800 net_read_timeout604800 log-error/var/lib/mysql/mysqld.log slow_qu…...
【Java-==与equals】
与equals区别: 1.是关系运算符,equals()是0bject类中定义的方法 2.基本数据类型: 使用比较值,无法使用equals() 3.引用数据类型: 使用比较内存地址; 如果没有重写equals(),仍然调用的是0bject父类的equals(()方法,则比较的是内…...
ai回答 部署前端项目时需要使用ssh吗
SSH(Secure Shell)是一种网络协议,用于在不安全的网络上安全地访问远程计算机。SSH 提供了加密的命令行登录到远程计算机的功能,以及远程命令执行和其他服务。它常用于系统管理员管理服务器、开发者进行远程开发、用户通过终端访问…...
结合ChatGPT与Discord,提高团队合作效率
本文将教你如何集成Discord Bot,助力团队在工作中实现更高效的沟通与协作。通过充分发挥ChatGPT的潜力,进一步提升工作效率和团队协作能力。无需编写任何代码即可完成本文所述的操作,进行个性化定制只需对参数进行微调即可。 方案介绍 如果在…...
VisualStudio|开发环境相关技巧及问题
哈喽,你好啊,我是雷工! 本节继续学习VisualStudio相关内容,以前学习都是以能用为主,没有系统的学习,接下来会系统的学习相关内容, 以下为学习笔记。 01 第三方dll调用 ①:如果第三…...
Redis远程字典服务器(11)—— redis客户端介绍
一,基本介绍 前面学习的主要是各种Redis的基本操作/命令,都是再Redis命令行客户端,手动执行的,但是这种方式不是我们日常开发中主要的形式更多的时候,是使用Redis的api,来实现定制化的Redis客户端程序&…...
【mysql】mysql之DDL数据定义语言
本站以分享各种运维经验和运维所需要的技能为主 《python零基础入门》:python零基础入门学习 《python运维脚本》: python运维脚本实践 《shell》:shell学习 《terraform》持续更新中:terraform_Aws学习零基础入门到最佳实战 《k8…...
Word文件密码忘记,该如何才能编辑Word文件呢?
Word文件打开之后,发现编辑功能都是灰色的,无法使用,无法编辑,遇到这种情况,是因为Word文件设置了限制编辑导致的。一般情况下,我们只需要输入Word密码,将限制编辑取消就可以正常编辑文件了&…...
解锁移动办公新境界,七款顶尖移动终端管控软件分享!助您轻松掌控每一台移动设备,企业必备!
移动办公,它不仅打破了时间和空间的限制,提高了工作效率,还为员工创造了更加灵活的工作环境。然而,随着移动设备的普及,如何有效管理和控制这些终端,确保信息安全、提升工作效率呢? 今天&#…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...
【杂谈】-递归进化:人工智能的自我改进与监管挑战
递归进化:人工智能的自我改进与监管挑战 文章目录 递归进化:人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管?3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...
376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...
【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)
升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点,但无自动故障转移能力,Master宕机后需人工切换,期间消息可能无法读取。Slave仅存储数据,无法主动升级为Master响应请求ÿ…...
在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...
springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
虚拟电厂发展三大趋势:市场化、技术主导、车网互联
市场化:从政策驱动到多元盈利 政策全面赋能 2025年4月,国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》,首次明确虚拟电厂为“独立市场主体”,提出硬性目标:2027年全国调节能力≥2000万千瓦࿰…...
