一、Spring 事务
1.1 什么是事务?
事务(Transaction)是数据库提供的一种机制,用来保证一组操作:
- 要么全部成功
- 要么全部失败
- 不会出现执行一半的情况
现实世界例子:你去银行转账:A 账户扣 100;B 账户加 100,如果扣成功,加失败怎么办?
-
钱不能凭空消失
-
要么都成功
-
要么都失败
这就叫:事务(Transaction)= 一组操作,要么全部成功,要么全部失败
1.2 数据库层面的事务(ACID)
事务的 4 个特性:
| 特性 | 含义 |
|---|---|
| A – 原子性 | 要么全成功,要么全失败 |
| C – 一致性 | 事务前后数据必须满足规则 |
| I – 隔离性 | 多个事务互不干扰 |
| D – 持久性 | 提交后数据永久保存 |
数据库本身(MySQL InnoDB)已经支持事务。
1.3 Spring 事务的核心价值没
有 Spring 时,事务怎么写?
在最原始的 JDBC 中:
Connection conn = dataSource.getConnection();
try {
conn.setAutoCommit(false);
// 执行SQL1
// 执行SQL2
conn.commit();
} catch (Exception e) {
conn.rollback();
} finally {
conn.close();
}
问题:
- 模板代码太多
- 容易忘记回滚
- 事务和业务代码强耦合
- 不利于维护
Spring 做了什么?
- 把事务管理从业务代码中剥离
- 用 AOP 自动帮我们做 begin / commit / rollback
- 支持声明式事务和编程式事务
二、Spring 事务的两种使用方式
Spring 事务 = 两种方式
| 类型 | 使用方式 | 本质 |
|---|---|---|
| 声明式事务 | @Transactional | AOP |
| 编程式事务 | TransactionTemplate | 手动控制 |
三,声明式事务
3.1 用法示例
核心注解:
@Transactional
示例:
@Service
public class OrderService {
@Transactional
public void createOrder() {
// 插入订单
// 扣库存
}
}
执行流程:
- Spring 创建代理对象(JDK / CGLIB)
- 调用方法时进入代理
- 代理开启事务
- 执行方法
- 判断异常
- 提交或回滚
3.2 @Transactional 全部参数详解
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,
readOnly = false,
timeout = -1,
rollbackFor = Exception.class,
noRollbackFor = RuntimeException.class
)
3.2.1 传播行为(Propagation)
事务传播 = 当前方法调用时如何处理已有事务
Spring 定义在:Spring Framework
Propagation 一共有 7 种。
REQUIRED(默认):有事务就加入,没有就创建
示例代码
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder() {
orderRepository.save(new Order("order1"));
logService.saveLogRequired();
throw new RuntimeException("出错了");
}
LogService:
@Transactional(propagation = Propagation.REQUIRED)
public void saveLogRequired() {
logRepository.save(new Log("log1"));
}
执行流程
- 调用 createOrder()
- 创建事务 T1
- 调用 saveLogRequired()
- 发现已有事务 → 加入 T1
- 抛异常
- 整个 T1 回滚
最终结果
| 表 | 数据 |
|---|---|
| orders | 无数据 |
| logs | 无数据 |
事务图
T1
├── order
└── log
使用场景
- 一个完整业务流程
- 子方法必须和主流程保持一致
REQUIRES_NEW:永远开启新事务,外层事务挂起。
示例代码
@Transactional
public void createOrder() {
orderRepository.save(new Order("order1"));
logService.saveLogRequiresNew();
throw new RuntimeException("出错");
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLogRequiresNew() {
logRepository.save(new Log("log1"));
}
执行流程
- 创建事务 T1
- 调用 saveLogRequiresNew()
- 挂起 T1
- 创建 T2
- T2 提交
- 恢复 T1
- T1 抛异常 → 回滚
最终结果
| 表 | 数据 |
|---|---|
| orders | 回滚 |
| logs | 存在 |
事务结构
T1 (order) 回滚
暂停
↓
T2 (log) 提交
原理解释:在 Spring Framework 中
- 使用 ThreadLocal 保存当前事务
- REQUIRES_NEW 会 suspend 当前事务
- 创建新的 Connection
- 新事务独立提交
使用场景
- 操作日志
- 审计记录
- 消息记录
- 不希望被主事务影响的操作
SUPPORTS:有事务就加入,没有就非事务执行
@Transactional(propagation = Propagation.SUPPORTS)
public void saveLogSupports() {
logRepository.save(new Log("log"));
}
- 外层有事务,加入外层事务。
- 外层没有事务,自动提交(非事务)。
使用场景,查询方法常用:
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
MANDATORY:必须在事务内执行,否则报错
@Transactional(propagation = Propagation.MANDATORY)
public void saveLogMandatory() {
logRepository.save(new Log("log"));
}
如果外层没有事务:
IllegalTransactionStateException
使用场景
- 强依赖主业务流程
- 不允许独立执行
NOT_SUPPORTED:不能在事务中执行
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void nonTransactionMethod() {
logRepository.save(new Log("log"));
}
执行:
- 外层事务挂起
- 当前方法非事务执行
- 自动提交
使用场景
- 大批量查询
- 报表统计
- 避免锁表
NEVER:如果有事务 → 报错
@Transactional(propagation = Propagation.NEVER)
public void test() {}
NESTED :嵌套事务(SavePoint)
注意:
- 只支持 JDBC
- 必须是 DataSourceTransactionManager
为什么必须是 DataSourceTransactionManager?
Spring 有多个事务管理器:
事务管理器 用途 DataSourceTransactionManager JDBC JpaTransactionManager JPA HibernateTransactionManager Hibernate JtaTransactionManager 分布式事务 Savepoint 是谁提供的?
答案:java.sql.Connection
JDBC API 中有:
Savepoint savepoint = connection.setSavepoint(); connection.rollback(savepoint);
示例
@Transactional
public void createOrder() {
orderRepository.save(new Order("order1"));
try {
logService.saveLogNested();
} catch (Exception e) {}
}
@Transactional(propagation = Propagation.NESTED)
public void saveLogNested() {
logRepository.save(new Log("log"));
throw new RuntimeException();
}
执行流程
- T1 创建
- 创建 SavePoint
- 子事务异常
- 回滚到 SavePoint
- T1 继续执行
- T1 提交
最终结果
| 表 | 数据 |
|---|---|
| orders | 存在 |
| logs | 无 |
事务结构
T1
├── order
└── savepoint
└── log
3.2.2 事务隔离级别(Isolation)
隔离级别决定并发时数据可见性。
对应数据库标准:
| 级别 | 解决问题 |
|---|---|
| READ_UNCOMMITTED | 脏读 |
| READ_COMMITTED | 不可重复读 |
| REPEATABLE_READ | 幻读 |
| SERIALIZABLE | 完全隔离 |
不同数据库默认不同:
- MySQL 默认 REPEATABLE_READ
- Oracle Corporation 默认 READ_COMMITTED
3.2.3 回滚规则
Spring 默认:只回滚 RuntimeException 和 Error
不会回滚:
throw new Exception();
如果要回滚受检异常:
@Transactional(rollbackFor = Exception.class)
3.2.4 readOnly 属性
@Transactional(readOnly = true)
作用:
- 提示数据库优化
- 禁止写操作(某些数据库)
但它不是强制限制,他只是提示优化!
3.2.5 timeout
@Transactional(timeout = 3)
3 秒没执行完 → 回滚。
四,编程式事务
4.1 用法示例
使用:
@Autowired
private TransactionTemplate transactionTemplate;
transactionTemplate.execute(status -> {
try {
// 业务逻辑
} catch (Exception e) {
status.setRollbackOnly();
}
return null;
});
优点:
- 可以精确控制某一段代码
- 不受 AOP 限制
- 适合复杂流程
4.2 为什么需要编程式事务?
声明式事务有一个天然限制:它基于 AOP,只能控制“方法级别”的事务边界。
但现实业务中常见场景:
- 一部分代码要提交,后面代码失败不影响前面
- 循环里每条数据单独事务
- try/catch 里精确控制回滚
- 动态决定是否开启事务
- 跨多个逻辑块
这时候:声明式事务就不够用了!
4.3 Spring 提供的两种编程式事务方式
4.3.1 TransactionTemplate
-
基础使用
@Autowired private TransactionTemplate transactionTemplate; public void test() { transactionTemplate.execute(status -> { orderMapper.insert(...); return null; }); } -
执行流程
execute() ↓ getTransaction() ↓ 执行回调 ↓ commit / rollback -
回滚控制
默认:
- 抛 RuntimeException → 回滚
- 正常返回 → 提交
你也可以手动控制:
transactionTemplate.execute(status -> { try { orderMapper.insert(...); } catch (Exception e) { status.setRollbackOnly(); } return null; }); -
设置属性
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);// 传播行为 transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ); // 隔离机制 transactionTemplate.setTimeout(5); transactionTemplate.setReadOnly(true);
它等价于:
@Transactional(...)
但你是手动控制的。
4.3.2 PlatformTransactionManager
这是最原始的事务控制。
@Autowired
private PlatformTransactionManager txManager;
-
手动完整写法
public void test() { DefaultTransactionDefinition definition = new DefaultTransactionDefinition(); definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); TransactionStatus status = txManager.getTransaction(definition); try { orderMapper.insert(...); txManager.commit(status); } catch (Exception e) { txManager.rollback(status); } } -
这和 JDBC 有什么区别?
-
JDBC
conn.setAutoCommit(false); conn.commit(); -
Spring
txManager.getTransaction() txManager.commit()Spring 帮你:
- 管理连接
- 管理线程绑定
- 管理嵌套事务
- 管理传播行为
-
4.4 编程式事务 vs 声明式事务对比
| 对比 | 声明式 | 编程式 |
|---|---|---|
| 控制粒度 | 方法级 | 代码块级 |
| 是否 AOP | 是 | 否 |
| 是否支持 self 调用 | 否 | 是 |
| 复杂逻辑控制 | 弱 | 强 |
| 可读性 | 高 | 一般 |
五,Spring 事务的底层原理
5.1 核心流程
在 Spring Framework 中:Spring 事务 = AOP 代理 + 事务拦截器 + 事务管理器 + ThreadLocal 资源绑定
从调用开始:一次事务到底发生了什么?
假设代码:
@Service
public class OrderService {
@Transactional
public void createOrder() {
orderMapper.insert(...);
}
}
当调用:
orderService.createOrder();
真正的执行流程是:
调用代理对象
↓
TransactionInterceptor
↓
获取事务管理器
↓
创建事务
↓
绑定 Connection 到 ThreadLocal
↓
执行目标方法
↓
commit / rollback
↓
清理 ThreadLocal
-
代理是怎么来的?
Spring 在启动时会解析:
@Transactional然后通过 AOP 为 Bean 创建代理对象。
如果类实现接口 → JDK 动态代理
否则 → CGLIB 代理所以:调用的不是原始对象,而是代理对象。
-
进入 TransactionInterceptor
事务增强的核心类是:
TransactionInterceptor它实现了 AOP 的
MethodInterceptor。核心方法:
invoke(MethodInvocation invocation)执行流程:
public Object invoke(MethodInvocation invocation) { // 1. 获取事务属性 TransactionAttribute txAttr = getTransactionAttribute(); // 2. 获取事务管理器 PlatformTransactionManager tm = determineTransactionManager(); // 3. 开启事务 TransactionInfo txInfo = createTransactionIfNecessary(); try { // 4. 执行目标方法 Object retVal = invocation.proceed(); // 5. 提交 commitTransactionAfterReturning(txInfo); return retVal; } catch (Throwable ex) { // 6. 回滚 completeTransactionAfterThrowing(txInfo, ex); throw ex; } }这就是核心流程。
-
创建事务
真正的创建逻辑在:
PlatformTransactionManager最常见实现类:
DataSourceTransactionManager核心方法:
getTransaction(TransactionDefinition definition)执行流程:
判断当前线程是否已有事务 ↓ 根据传播行为决定: - 加入 - 新建 - 挂起 ↓ 如果新建: doBegin() -
doBegin() 做了什么?
在 DataSourceTransactionManager 里:
protected void doBegin(...) { Connection con = dataSource.getConnection(); con.setAutoCommit(false); if (definition.isReadOnly()) { con.setReadOnly(true); } // 绑定到线程 TransactionSynchronizationManager.bindResource(dataSource, connectionHolder); } -
ThreadLocal 资源绑定(核心)
Spring 用:
TransactionSynchronizationManager内部有:
private static final ThreadLocal<Map<Object, Object>> resources;作用:把 Connection 绑定到当前线程。
这意味着:
- 同一个线程
- 同一个事务
- 同一个数据库连接
-
执行目标方法
当执行:
orderMapper.insert(...)MyBatis 或 JPA 获取连接时:不会新建连接,而是从:
TransactionSynchronizationManager中获取已经绑定的连接。
所以:同一事务内所有 SQL 使用同一个 Connection
-
提交流程
当方法正常返回:
commitTransactionAfterReturning()内部调用:
txManager.commit(status);在
DataSourceTransactionManager中:protected void doCommit(DefaultTransactionStatus status) { Connection con = ... con.commit(); }然后:
- 解绑 ThreadLocal
- 关闭连接
- 清理同步器
-
回滚流程
如果发生异常:
completeTransactionAfterThrowing()内部:
txManager.rollback(status);最终:
con.rollback();
完整流程图
@Transaction 方法调用
↓
AOP 代理
↓
TransactionInterceptor
↓
PlatformTransactionManager.getTransaction()
↓
doBegin()
↓
绑定 Connection 到 ThreadLocal
↓
执行 SQL
↓
是否异常?
↓ ↓
commit rollback
↓
清理 ThreadLocal
5.2 PlatformTransactionManager 体系
核心接口:
PlatformTransactionManager
常见实现:
| 实现类 | 场景 |
|---|---|
| DataSourceTransactionManager | 单数据源 |
| JpaTransactionManager | JPA |
| HibernateTransactionManager | Hibernate |
| JtaTransactionManager | 分布式事务 |
六,事务失效的 8 大常见原因
在 Spring Framework 中:Spring 事务本质是 AOP 代理。所以,所有事务失效,本质都围绕一个核心:是否经过代理对象
6.1 方法不是 public
错误示例
@Transactional
private void createOrder() {
orderMapper.insert(...);
}
为什么失效?
Spring 默认使用 AOP 代理:
- JDK 动态代理 → 只能代理接口 public 方法
- CGLIB → 也只拦截 public 方法
private 方法:
- 不会被代理增强
- 不会进入 TransactionInterceptor
6.2 self 调用
示例
public void outer() {
this.inner(); // 事务失效
}
@Transactional
public void inner() {
orderMapper.insert(...);
}
为什么失效?
调用路径:
this.inner()
没有走代理对象。
代理结构:
代理对象
↓
真实对象
self 调用时:
真实对象 → 真实对象
绕过了代理。
解决方案
-
注入自己
@Autowired private OrderService self; self.inner(); -
使用 AopContext
((OrderService) AopContext.currentProxy()).inner();
6.3 final 方法
示例
@Transactional
public final void test() {}
为什么失效?
CGLIB 原理:通过继承目标类,重写方法,而 final 方法不能被重写,所以无法增强。
6.4 static 方法
示例
@Transactional
public static void test() {}
原因
AOP 是基于对象代理。
static 方法:
- 不属于对象
- 无法代理
6.5 异常被try-catch吃掉
示例
@Transactional
public void test() {
try {
orderMapper.insert(...);
int i = 1 / 0;
} catch (Exception e) {
// 什么也没做
}
}
为什么失效?
Spring 事务判断回滚依据:方法是否抛出异常
如果异常被吞掉:
- 方法正常返回
- Spring 执行 commit
解决方案
-
手动回滚
catch (Exception e) { TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } -
将异常抛出
6.6 非 Spring 管理对象
示例
OrderService service = new OrderService();
service.createOrder();
为什么失效?
Spring 事务依赖:
Spring 容器管理的 Bean
手动 new 的对象:
- 没有代理
- 没有事务增强
正确方式
@Autowired
private OrderService service;
6.7 多线程
示例
@Transactional
public void test() {
new Thread(() -> {
orderMapper.insert(...);
}).start();
}
为什么失效?
Spring 使用:ThreadLocal
-
绑定事务资源。
-
事务只存在当前线程。
新线程:
- 没有绑定 Connection
- 不在事务内
解决方案
- 不在事务里开线程
- 或者使用分布式事务
- 或者编程式事务控制
6.8 数据库不支持事务(MyISAM)
例如在 MySQL 中:
如果表使用:MyISAM
则:
- 不支持事务
- commit / rollback 无效
必须使用:InnoDB