0%

spring事务

Spring 事务深度解析:从 ACID 到分布式事务

Spring 事务是企业级应用中确保数据一致性的核心机制,基于 AOP(面向切面编程) 实现,将事务管理逻辑与业务逻辑解耦。它不仅支持本地事务(单数据库),还通过 JTA 等规范支持分布式事务(跨数据库 / 服务)。从 “事务基础→核心属性→实现原理→实战配置→分布式事务” 五个维度,系统拆解 Spring 事务的工作机制与最佳实践。

事务基础:理解 ACID 与并发问题

事务(Transaction)是数据库操作的最小单元,必须满足 ACID 特性,同时需解决并发场景下的脏读、不可重复读、幻读问题。

1. 事务的 ACID 特性

ACID 是事务的四大核心特性,确保数据操作的一致性与可靠性:

特性 英文全称 核心含义 示例场景(转账:A 转 100 给 B)
原子性(Atomicity) Atomicity 事务中的操作 “要么全成功,要么全失败”,无中间状态 A 扣款 100 成功,但 B 到账 100 失败 → 事务回滚,A 余额恢复
一致性(Consistency) Consistency 事务执行前后,业务状态需符合预期(数据完整性约束不被破坏) 转账前 A+B 余额 = 2000 → 转账后仍为 2000(不会出现 A 扣了但 B 没到账)
隔离性(Isolation) Isolation 多个事务并发执行时,彼此隔离,互不干扰(避免并发问题) 事务 1 读取 A 余额时,事务 2 修改 A 余额 → 事务 1 读取的是隔离后的数据
持久性(Durability) Durability 事务提交后,结果永久保存到数据库(即使数据库崩溃,数据也不丢失) 转账成功后,即使数据库重启,A 扣 100、B 加 100 的结果仍存在

2. 并发事务的三大问题

当多个事务同时操作同一批数据时,若隔离性不足,会引发三类典型问题:

并发问题 具体表现 示例(事务 T1 读取数据,事务 T2 修改数据)
脏读(Dirty Read) T1 读取 T2 未提交的修改 → T2 回滚后,T1 读取的是 “无效数据” T2 给 A 加 100(未提交)→ T1 读 A 余额为 1100 → T2 回滚 → T1 基于 1100 做业务,结果错误
不可重复读(Non-Repeatable Read) T1 多次读取同一数据 → T2 提交修改后,T1 两次读取结果不一致 T1 第一次读 A 余额 1000 → T2 给 A 加 100(提交)→ T1 第二次读 A 余额 1100,结果不同
幻读(Phantom Read) T1 按条件查询数据 → T2 插入 / 删除符合条件的行 → T1 再次查询,行数变化 T1 查 “余额> 500 的用户”(2 人)→ T2 新增 1 个余额 600 的用户(提交)→ T1 再次查询,结果为 3 人

3. 事务隔离级别:解决并发问题

数据库通过 隔离级别 控制并发事务的干扰程度,Spring 支持数据库的所有隔离级别,并新增 DEFAULT(继承数据库默认级别)。不同级别对并发问题的解决能力不同,性能也不同:

隔离级别 解决脏读 解决不可重复读 解决幻读 性能 数据库默认(常见)
READ_UNCOMMITTED 最高
READ_COMMITTED 较高 Oracle、SQL Server
REPEATABLE_READ 中等 MySQL(InnoDB)
SERIALIZABLE 最低
DEFAULT(Spring 新增) 继承数据库默认级别 继承数据库默认级别 继承数据库默认级别 取决于数据库 -

注意:隔离级别越高,并发能力越弱(锁机制更严格),需在 “数据一致性” 与 “性能” 间平衡。例如:金融核心业务用 REPEATABLE_READ,非核心查询用 READ_COMMITTED

Spring 事务核心属性:定制事务行为

Spring 事务通过 TransactionDefinition 接口定义核心属性,包括传播行为、隔离级别、超时时间、只读属性、回滚规则,这些属性决定了事务的执行逻辑。

1. 事务传播行为(核心)

传播行为定义了 “当一个事务方法调用另一个事务方法时,如何处理事务上下文”,是 Spring 事务最灵活的特性之一,共 7 种:

传播行为常量 核心含义 适用场景
PROPAGATION_REQUIRED 支持当前事务;若当前无事务,新建事务;若有事务,加入当前事务(默认值) 绝大多数业务场景(如转账、下单)
PROPAGATION_SUPPORTS 支持当前事务;若当前无事务,以非事务方式执行 可选事务的查询方法(如 “获取订单详情”,有事务则加入,无则非事务)
PROPAGATION_MANDATORY 支持当前事务;若当前无事务,抛出异常(强制要求事务上下文) 必须在事务中执行的方法(如 “扣减库存”,防止调用者遗漏事务)
PROPAGATION_REQUIRES_NEW 新建事务;若当前有事务,将当前事务挂起(新事务与原事务独立) 独立事务(如 “记录操作日志”,即使主事务回滚,日志仍需提交)
PROPAGATION_NOT_SUPPORTED 以非事务方式执行;若当前有事务,挂起当前事务 无需事务的操作(如 “发送短信通知”,避免事务过长影响性能)
PROPAGATION_NEVER 以非事务方式执行;若当前有事务,抛出异常(禁止事务上下文) 绝对不能在事务中执行的方法(如 “数据备份”,防止锁表)
PROPAGATION_NESTED 若当前有事务,在嵌套事务中执行(嵌套事务可独立回滚);若无事务,新建事务 部分回滚场景(如 “下单”:扣库存失败回滚,但生成订单记录不回滚)
传播行为示例:REQUIRED vs REQUIRES_NEW
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Service
public class OrderService {
@Autowired
private InventoryService inventoryService;

// 主事务:传播行为 REQUIRED
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder() {
// 1. 生成订单(主事务内)
System.out.println("生成订单");
try {
// 2. 调用库存服务(传播行为 REQUIRES_NEW,新建独立事务)
inventoryService.deductStock();
} catch (Exception e) {
// 3. 库存扣减失败,仅回滚 deductStock() 的独立事务,主事务继续执行
System.out.println("库存扣减失败,订单仍保留");
}
}
}

@Service
public class InventoryService {
// 子事务:传播行为 REQUIRES_NEW(与主事务独立)
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void deductStock() {
System.out.println("扣减库存");
throw new RuntimeException("库存不足"); // 子事务回滚
}
}

结果deductStock() 回滚(库存不变),createOrder() 继续执行(订单生成成功)。

2. 其他核心属性

属性 作用 配置示例
隔离级别(isolation) 控制并发事务的隔离程度 @Transactional(isolation = Isolation.REPEATABLE_READ)
只读(readOnly) 标记事务仅执行查询操作(数据库优化:避免加写锁) @Transactional(readOnly = true)(仅查询方法用)
超时时间(timeout) 事务超时时间(秒),超时后自动回滚(防止长事务占用资源) @Transactional(timeout = 30)(30 秒超时)
回滚规则(rollbackFor) 指定哪些异常触发事务回滚(默认仅回滚运行时异常,如 RuntimeException) @Transactional(rollbackFor = Exception.class)(所有异常回滚)
不回滚规则(noRollbackFor) 指定哪些异常不触发回滚 @Transactional(noRollbackFor = BusinessException.class)(业务异常不回滚)

注意:readOnly=true 不可用于修改操作(如 insert/update/delete),否则可能导致数据不一致(数据库可能忽略写操作或不加锁)。

Spring 事务实现原理:基于 AOP 的动态代理

Spring 事务的底层是 AOP 动态代理,通过 PlatformTransactionManager(事务管理器)实现事务的 “开启→提交→回滚” 生命周期管理。

1. 核心接口:PlatformTransactionManager

PlatformTransactionManager 是 Spring 事务的顶层接口,定义了事务管理的三大核心操作,不同持久化框架有不同实现:

1
2
3
4
5
6
7
8
9
10
public interface PlatformTransactionManager {
// 1. 获取事务(根据 TransactionDefinition 配置,开启事务或加入现有事务)
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;

// 2. 提交事务(若事务正常完成,提交;若有保存点或异常,回滚)
void commit(TransactionStatus status) throws TransactionException;

// 3. 回滚事务(事务异常时触发)
void rollback(TransactionStatus status) throws TransactionException;
}
常用实现类(按持久化框架分类):
实现类 适用框架 核心作用
DataSourceTransactionManager MyBatis、Spring JDBC 管理 JDBC 连接的事务(基于 Connection)
HibernateTransactionManager Hibernate 管理 Hibernate Session 的事务
JpaTransactionManager JPA 管理 JPA EntityManager 的事务
JtaTransactionManager JTA(分布式事务) 管理跨数据库 / 服务的分布式事务

2. 事务执行流程(AOP 动态代理)

Spring 事务通过 AOP 生成代理对象,在目标方法执行前后插入事务管理逻辑,流程如下:

graph TD
    A[调用代理对象的事务方法] --> B["获取 TransactionDefinition(隔离级别、传播行为等)"]
    B --> C["PlatformTransactionManager.getTransaction():开启/加入事务"]
    C --> D["执行目标方法(业务逻辑)"]
    D --> E{方法执行结果}
    E -->|正常完成| F["PlatformTransactionManager.commit():提交事务"]
    E -->|抛出异常| G["PlatformTransactionManager.rollback():回滚事务"]
    F --> H[方法执行结束]
    G --> H
关键细节:
  • 代理对象生成:若目标类实现接口,用 JDK 动态代理;否则用 CGLIB 动态代理;
  • 事务上下文传递:通过 ThreadLocal 存储当前事务状态(TransactionStatus),确保同一线程内的事务方法共享事务上下文;
  • 异常判断:仅当异常符合 rollbackFor 配置时,才触发回滚(默认仅回滚运行时异常)。

Spring 事务实战:注解与 XML 配置

Spring 事务支持两种配置方式:注解驱动(推荐,Spring Boot 常用)XML 配置(传统方式),核心是 “配置事务管理器 + 定义事务属性”。

前置准备:环境与依赖

1. 数据库表与初始数据
1
2
3
4
5
6
7
8
9
-- 新建用户表(转账场景)
CREATE TABLE user (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20) NOT NULL,
account DOUBLE NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- 插入初始数据(A 和 B 各 1000 元)
INSERT INTO user (name, account) VALUES ('张三', 1000), ('李四', 1000);
2. 依赖配置(Maven)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!-- Spring 核心依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.29.RELEASE</version>
</dependency>

<!-- Spring JDBC(数据源+事务依赖) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.29.RELEASE</version>
</dependency>

<!-- Spring 事务核心依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.3.29.RELEASE</version>
</dependency>

<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
</dependency>

<!-- Spring AOP(事务基于 AOP 实现) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.29.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>

方式一:注解驱动配置(推荐)

通过 @Transactional 注解标记事务方法,配合 tx:annotation-driven 启用自动代理,步骤如下:

1. 配置 Spring 上下文(XML)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

<!-- 1. 组件扫描(扫描 @Service、@Component 等) -->
<context:component-scan base-package="com.zhanghe.study.spring4.beans.tx"/>

<!-- 2. 配置数据源(DriverManagerDataSource 仅用于测试,生产用 Druid/C3P0) -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring?useUnicode=true&amp;characterEncoding=utf-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>

<!-- 3. 配置事务管理器(MyBatis/JDBC 用 DataSourceTransactionManager) -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/> <!-- 关联数据源 -->
</bean>

<!-- 4. 启用事务注解(自动扫描 @Transactional 注解,生成代理对象) -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
2. 编写事务服务类(@Transactional)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class TransferService {
// 注入 JdbcTemplate(简化 JDBC 操作)
@Autowired
private JdbcTemplate jdbcTemplate;

/**
* 转账事务方法:张三给李四转 100 元
* 配置:REQUIRED 传播行为、REPEATABLE_READ 隔离级别、所有异常回滚
*/
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.REPEATABLE_READ,
rollbackFor = Exception.class // 所有异常都回滚(默认仅回滚运行时异常)
)
public void transfer() {
try {
// 1. 张三扣款 100 元
jdbcTemplate.update("UPDATE user SET account = account - 100 WHERE name = '张三'");

// 2. 模拟异常(测试回滚):取消注释后,事务会回滚,张三和李四余额不变
// throw new RuntimeException("转账失败,模拟异常");

// 3. 李四到账 100 元
jdbcTemplate.update("UPDATE user SET account = account + 100 WHERE name = '李四'");
} catch (Exception e) {
// 异常会被 Spring 捕获,触发事务回滚
throw new RuntimeException("转账失败:" + e.getMessage());
}
}

/**
* 只读查询方法:查询用户余额(配置 readOnly=true 优化性能)
*/
@Transactional(readOnly = true)
public Double getAccount(String name) {
return jdbcTemplate.queryForObject(
"SELECT account FROM user WHERE name = ?",
new Object[]{name},
Double.class
);
}
}
3. 测试事务效果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TransactionTest {
public static void main(String[] args) {
// 1. 初始化 Spring 容器
ApplicationContext context = new ClassPathXmlApplicationContext("spring-tx.xml");

// 2. 获取事务服务类(实际是代理对象)
TransferService transferService = context.getBean(TransferService.class);

// 3. 转账前查询余额
System.out.println("转账前:张三余额=" + transferService.getAccount("张三")
+ ",李四余额=" + transferService.getAccount("李四"));

try {
// 4. 执行转账
transferService.transfer();
System.out.println("转账成功!");
} catch (Exception e) {
System.out.println("转账失败:" + e.getMessage());
}

// 5. 转账后查询余额
System.out.println("转账后:张三余额=" + transferService.getAccount("张三")
+ ",李四余额=" + transferService.getAccount("李四"));
}
}

测试结果

  • 无异常时:张三余额 900,李四余额 1100(事务提交);
  • 有异常时:张三和李四余额仍为 1000(事务回滚)。

方式二:XML 配置(传统方式)

通过 <tx:advice> 定义事务属性,<aop:config> 关联切入点与事务通知,无需注解,步骤如下:

1. XML 配置(核心是事务通知 + AOP 切面)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 1. 组件扫描 -->
<context:component-scan base-package="com.zhanghe.study.spring4.beans.tx"/>

<!-- 2. 配置数据源(同注解方式) -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring?useUnicode=true&amp;characterEncoding=utf-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>

<!-- 3. 配置事务管理器(同注解方式) -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

<!-- 4. 配置事务通知(tx:advice):定义事务属性 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 4.1 匹配“get*”“find*”开头的方法:只读事务 -->
<tx:method name="get*" read-only="true" isolation="REPEATABLE_READ"/>
<tx:method name="find*" read-only="true" isolation="REPEATABLE_READ"/>

<!-- 4.2 匹配“transfer”“save*”“update*”开头的方法:REQUIRED 传播行为,所有异常回滚 -->
<tx:method name="transfer" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="save*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="update*" propagation="REQUIRED" rollback-for="Exception"/>

<!-- 4.3 其他方法:默认配置 -->
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>

<!-- 5. 配置 AOP 切面:关联切入点与事务通知 -->
<aop:config>
<!-- 5.1 定义切入点:匹配 TransferService 的所有方法 -->
<aop:pointcut id="txPointcut"
expression="execution(* com.zhanghe.study.spring4.beans.tx.TransferService.*(..))"/>

<!-- 5.2 关联切面:切入点 txPointcut 应用事务通知 txAdvice -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
</beans>
2. 服务类(无注解)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Service
public class TransferService {
@Autowired
private JdbcTemplate jdbcTemplate;

// 无需 @Transactional 注解,XML 已配置事务属性
public void transfer() {
// 逻辑同注解方式(张三扣款→李四到账)
}

// 无需 @Transactional 注解,XML 已配置 readOnly=true
public Double getAccount(String name) {
// 逻辑同注解方式
}
}

测试效果:与注解方式完全一致,XML 配置通过 “方法名匹配” 替代了注解,更适合不允许使用注解的传统项目。

分布式事务:跨数据库 / 服务的一致性

当业务涉及 分库分表跨服务调用 时,本地事务无法保证数据一致性,需使用 分布式事务。Spring 基于 JTA(Java Transaction API)规范支持分布式事务,常用实现有 Atomikos、Bitronix 等。

1. 分布式事务场景

  • 分库场景:订单数据存在 DB1,库存数据存在 DB2,下单时需同时修改 DB1 和 DB2;
  • 微服务场景:用户服务(修改用户余额)调用订单服务(创建订单),需保证两个服务的事务同时提交或回滚。

2. Spring 分布式事务实现(Atomikos)

Spring Boot 中通过 spring-boot-starter-jta-atomikos 快速集成分布式事务,步骤如下:

1. 依赖配置(Maven)
1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
<version>2.7.17</version> <!-- 与 Spring Boot 版本匹配 -->
</dependency>
2. 配置多数据源(application.yml)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
spring:
jta:
atomikos:
properties:
service: com.atomikos.icatch.standalone.UserTransactionServiceFactory
max-timeout: 300000 # 事务超时时间(毫秒)
datasource:
# 数据源 1(订单库)
order:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/order_db?useUnicode=true&characterEncoding=utf-8
username: root
password: 123456
unique-resource-name: orderDataSource # 唯一资源名(分布式事务标识)
# 数据源 2(库存库)
inventory:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/inventory_db?useUnicode=true&characterEncoding=utf-8
username: root
password: 123456
unique-resource-name: inventoryDataSource
3. 配置分布式事务管理器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import com.atomikos.jdbc.AtomikosDataSourceBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.jta.JtaTransactionManager;

import javax.sql.DataSource;
import java.util.Properties;

@Configuration
public class DistributedTxConfig {
// 1. 配置订单库数据源(Atomikos 管理)
@Bean
@ConfigurationProperties(prefix = "spring.datasource.order")
public DataSource orderDataSource() {
AtomikosDataSourceBean dataSource = new AtomikosDataSourceBean();
Properties props = new Properties();
props.setProperty("url", "jdbc:mysql://localhost:3306/order_db?useUnicode=true&characterEncoding=utf-8");
props.setProperty("user", "root");
props.setProperty("password", "123456");
dataSource.setXaProperties(props);
dataSource.setUniqueResourceName("orderDataSource");
return dataSource;
}

// 2. 配置库存库数据源(Atomikos 管理)
@Bean
@ConfigurationProperties(prefix = "spring.datasource.inventory")
public DataSource inventoryDataSource() {
AtomikosDataSourceBean dataSource = new AtomikosDataSourceBean();
Properties props = new Properties();
props.setProperty("url", "jdbc:mysql://localhost:3306/inventory_db?useUnicode=true&characterEncoding=utf-8");
props.setProperty("user", "root");
props.setProperty("password", "123456");
dataSource.setXaProperties(props);
dataSource.setUniqueResourceName("inventoryDataSource");
return dataSource;
}

// 3. 配置 JTA 事务管理器(分布式事务核心)
@Bean
public JtaTransactionManager jtaTransactionManager() {
return new JtaTransactionManager();
}

// 4. 配置 JdbcTemplate(分别关联两个数据源)
@Bean
public JdbcTemplate orderJdbcTemplate() {
return new JdbcTemplate(orderDataSource());
}

@Bean
public JdbcTemplate inventoryJdbcTemplate() {
return new JdbcTemplate(inventoryDataSource());
}
}
4. 分布式事务服务类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Service
public class DistributedOrderService {
@Autowired
private JdbcTemplate orderJdbcTemplate;

@Autowired
private JdbcTemplate inventoryJdbcTemplate;

// 分布式事务:同时操作订单库和库存库
@Transactional(rollbackFor = Exception.class)
public void createOrderWithInventory() {
try {
// 1. 订单库:创建订单
orderJdbcTemplate.update("INSERT INTO `order` (order_no, user_id) VALUES ('ORDER001', 1)");

// 2. 库存库:扣减库存
inventoryJdbcTemplate.update("UPDATE inventory SET stock = stock - 1 WHERE goods_id = 1");

// 3. 模拟异常(测试分布式回滚)
// throw new RuntimeException("分布式事务异常");
} catch (Exception e) {
throw new RuntimeException("分布式事务失败:" + e.getMessage());
}
}
}

效果:若任一数据源操作失败,两个数据源的事务会同时回滚,确保数据一致性。

3. 分布式事务注意事项

  • 性能问题:分布式事务需协调多个数据库 / 服务,锁机制更严格,性能远低于本地事务,避免过度使用;
  • 最终一致性:对于高并发场景,优先采用 “最终一致性” 方案(如 RocketMQ 事务消息、Seata TCC/SAGA 模式),而非强一致性的 JTA 事务;
  • 资源释放:确保分布式事务超时时间合理,避免长时间占用数据库连接或网络资源。

Spring 事务常见问题与避坑指南

  1. @Transactional 注解不生效
    • 原因 1:方法为 private/static/final(动态代理无法代理这些方法);
    • 原因 2:异常被 try-catch 捕获且未重新抛出(Spring 无法感知异常,不触发回滚);
    • 原因 3:未启用 tx:annotation-driven@EnableAspectJAutoProxy
    • 解决方案:方法用 public 修饰,异常捕获后重新抛出,确保自动代理启用。
  2. 事务传播行为选择错误
    • 例:子事务用 REQUIRED 时,主事务回滚会导致子事务也回滚(若需子事务独立,用 REQUIRES_NEW);
    • 解决方案:根据业务场景选择传播行为,核心业务用 REQUIRED,独立操作(如日志)用 REQUIRES_NEW
  3. 隔离级别设置过高
    • 例:用 SERIALIZABLE 导致并发能力急剧下降(锁表严重);
    • 解决方案:非核心业务用 READ_COMMITTED,核心业务用 REPEATABLE_READ,避免 SERIALIZABLE

总结:Spring 事务的核心价值

Spring 事务通过 “声明式配置” 与 “AOP 动态代理”,实现了事务管理与业务逻辑的解耦,其核心价值体现在:

  1. 简化开发:无需手动编写 Connection.commit()/rollback(),注解 / XML 配置即可;
  2. 灵活定制:支持 7 种传播行为、5 种隔离级别,满足复杂业务场景;
  3. 一致性保障:本地事务确保单库一致性,分布式事务支持跨库 / 服务一致性;
  4. 兼容性强:适配 MyBatis、Hibernate、JPA 等主流持久化框架

欢迎关注我的其它发布渠道