Seata事务回滚失败问题定位解决
先描述一下问题,别没用还往下看耽误大家时间:
问题现象就是调用的方法加了GlobalTransactional注解,但是出现异常之后,期望seata帮我回滚数据库,实际上却没有回滚。并且seata的服务器回显了日志回滚成功,但是我去数据库查发现两个表的数据都刷新成功,并没有回显。日志如下所示:
00:53:22.174 INFO --- [verHandlerThread_1_20_500] io.seata.server.coordinator.DefaultCore
: Rollback global transaction successfully, xid = ip:8091:1745452184813340216.
问题根因
我这边前面准备工作很充分,undo_log是有这个表的,经过deepseek对现象的一通分析发现是seata没有正确代理我的数据源DataSource。(很明显的现象就是undo_log在这个过程中一直没有任何数据)
问题解决
解决就是能让seata代理数据源。有两种方式,一种是yml增加配置
seata:
enable-auto-data-source-proxy: true
这个我加了之后没效果,一脸懵,去日志查询dataSource还是没有被代理,然后就用方法二,手动加代理,DataSourceConfig,如下所示代码:(**用这个先关上面的哈,方法一改为false)**这里我因为启动时候看到是HikariDataSource ,所以专门加的这个类型的。
@Configuration
public class DataSourceConfig {
/**
* 1. 创建一个 HikariDataSource 实例
* 2. 使用 @ConfigurationProperties 将 application.yml 中所有 'spring.datasource' 开头的配置
* (如 url, username, password, hikari.* 等)自动绑定到这个数据源上。
* 3. 不需要在代码中手动设置任何属性!
*/
@Bean
@ConfigurationProperties(prefix = "spring.datasource") // 这是关键
public HikariDataSource hikariDataSource() {
return new HikariDataSource();
}
/**
* 4. 将 HikariDataSource 包装成 Seata 的 DataSourceProxy
* 5. 使用 @Primary 注解确保当有多个 DataSource Bean 时,优先注入这个
*/
@Bean
@Primary // 这个注解很重要
public DataSource dataSource(HikariDataSource hikariDataSource) { // 注入上面定义的Bean
return new DataSourceProxy(hikariDataSource);
}
}
这时候问题又来了,用这个手动增加代理的方式我这边启动报错,日志如下:
[/DataSourceConfig.class]: Bean instantiation via factory method failed;
nested exception is org.springframework.beans.BeanInstantiationException: Failed to
instantiate [javax.sql.DataSource]: Factory method 'dataSource' threw exception; nested
exception is java.lang.IllegalArgumentException: jdbcUrl is required with
driverClassName.
这个报错就很明显,意思是jdbcUrl查不到,,可是我前面明明通过@ConfigurationProperties(prefix = “spring.datasource”) 引入yml的配置了,为什么还没有?
再看看我的yml配置就清楚了。
spring:
datasource:
url: jdbc:mysql://localhost:3306/seata_order?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false #
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver # MySQL 8.x 的驱动类:cite[6]
这个配置很正常,但是发现是url而不是jdbcUrl,查询之后发现HikariDataSource 连接池的url正确配置是jdbc-url
spring:
datasource:
jdbc-url: jdbc:mysql://localhost:3306/seata_order?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false #
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver # MySQL 8.x 的驱动类:cite[6]
这样修改之后就可以启动,然后现象就符合预期了。问题解决。
日志也显示被seata代理了:
2025-08-29 01:32:25.992 INFO 15124 --- [ main] .s.s.a.d.SeataAutoDataSourceProxyCreator : Auto proxy of [hikariDataSource]
2025-08-29 01:32:26.022 ERROR 15124 --- [ main] com.zaxxer.hikari.HikariConfig : HikariPool-1 - dataSource or dataSourceClassName or jdbcUrl is required.
其他知识(方法二)
spring-boot-starter-jdbc 本身并不直接包含任何一个连接池的实现(如 HikariCP、DBCP2、Druid)。它只是一个“启动器”,它的作用是:引入一系列与 JDBC 操作相关的依赖(如 spring-jdbc, spring-tx)。
关键点在于:当 Spring Boot 尝试自动配置 DataSource 时,它会在项目的类路径(Classpath)上扫描,按优先级自动选择一个它发现的连接池实现。其默认优先级为:HikariCP > Tomcat JDBC Pool > Commons DBCP2。
其实这里也可以在 pom.xml 中显式添加一个连接池的依赖。添加后,不用HikariCP连接池,然后在yml文件增加seata: enable-auto-data-source-proxy: true 就可以删除DataSourceConfig文件了。(对我这种强迫症,能用配置就配置的人来说比较友好。。)
1、框架介绍(未完待定。。。)
今天搭建了一个springcloudaAlibaba+nacos+openFeign+seatad的项目,想验证一下是否能正确对分布式事务进行回滚。搭建时候数据库相关的只引入了这两个依赖(埋坑的开始。。。)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version> <!-- 与你的MySQL服务器版本一致 -->
<scope>runtime</scope>
</dependency>
项目本身很简单,一个Order服务,一个Stock服务,订单服务新增时候会调用库存服务的减库存接口,代码大概这样。
@RestController
@RequestMapping("/order")
public class OrderController {
@Resource
private StorageService storageService;
@Resource
private OrderServiceImpl orderService;
@RequestMapping("/add/{id}")
@GlobalTransactional
public String add(@PathVariable int id) {
OrderDao orderDao = new OrderDao();
int productId = 1;
int nums = 10;
orderDao.setId(id);
orderDao.setProductId(productId);
orderDao.setTotalAmount(nums);
Integer integer = orderService.insertOrder(orderDao);
System.out.println(integer);
String change = storageService.change(orderDao.getTotalAmount(),orderDao.getProductId());
System.out.println(1/0);
return "success" + change;
}
}
更多推荐

所有评论(0)