Spring事务机制
1. 这是什么
Spring 事务机制是在数据库事务能力之上,提供统一声明式事务管理的一套框架支持。
它让业务代码可以更方便地控制提交、回滚和传播行为。
一句话理解:
- 事务本质还是数据库事务
- Spring 做的是把事务边界和控制逻辑统一织入业务方法
2. 为什么重要
事务是后端业务正确性的核心手段之一。
如果不理解 Spring 事务机制,就很容易写出这些“看起来有事务、实际没生效”的代码:
- 抛异常却没回滚
- 自调用导致事务失效
- 长耗时逻辑把事务拖得很大
- 不知道传播行为为什么影响结果
3. 先建立直觉:Spring 事务不是脱离代理独立存在的
Spring 声明式事务最常见的实现路径是:
- 通过代理拦截方法调用
- 在方法执行前开启事务
- 方法成功后提交
- 方法异常时回滚
所以如果你不理解代理,就很难理解事务为什么有时生效、有时失效。
4. 核心内容
4.1 声明式事务是什么
声明式事务的核心思想是:
- 你描述事务边界
- 框架负责帮你织入事务控制逻辑
在 Spring 里,最常见的表现形式就是:
java
@Transactional它的价值在于:
- 把事务逻辑从业务代码中抽离出来
4.2 事务传播行为在解决什么
传播行为描述的是:
- 当前方法进入时,如果已经有事务了,该怎么办
例如:
- 加入现有事务
- 新开一个事务
- 强制非事务执行
学习阶段最重要的是先建立语义理解,而不是死背全部枚举。
4.3 回滚规则为什么不能想当然
很多人会误以为:
- 只要抛异常就一定回滚
但实际上回滚规则要结合:
- 异常类型
- 配置方式
- 框架默认规则
工程上最重要的结论是:
- 不能只看“报错了没有”
- 还要看这次异常是否真的触发了事务回滚逻辑
4.4 为什么自调用会导致事务失效
这和 AOP 一样,是事务里最经典的问题之一。
如果一个方法内部这样调用:
java
this.innerMethod();那通常不会经过代理对象,而是:
- 当前对象内部直接调用自己
结果就是:
- 事务增强可能根本没织进去
4.5 为什么长耗时逻辑不适合放进事务
事务一旦开启,就意味着:
- 锁可能持有更久
- 数据库资源占用更久
- 冲突概率更高
所以像这些操作通常要谨慎:
- 远程调用
- 大文件处理
- 长时间等待
5. 学习重点
这一章最重要的是掌握:
- Spring 事务本质上依赖数据库事务能力
- 声明式事务通常通过代理织入
- 传播行为是在描述事务边界如何组合
- 自调用和异常处理方式是两个高频失效点
- 事务范围越大,并发代价通常越高
6. 常见问题
6.1 自调用导致事务失效
这是最常见、也最容易误判的问题之一。
6.2 异常被捕获却没有回滚
如果异常已经被业务代码吞掉或改写,事务层就未必还能按预期感知到失败。
6.3 把长耗时逻辑放进事务里
这会放大锁占用时间和数据库压力。
7. 动手验证
这一节我用纯 Java 做一个“Spring 风格事务代理”的简化实验,重点演示两个现象:
- 代理入口调用时,异常能触发回滚
- 内部自调用时,事务增强不会自动织入
7.1 准备一个可运行示例
新建文件 SpringTransactionLikeDemo.java,内容如下:
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class SpringTransactionLikeDemo {
interface OrderService {
void outerSuccess();
void outerCallInner();
void innerFail();
}
static class TransactionManager {
void begin() {
System.out.println("tx-begin");
}
void commit() {
System.out.println("tx-commit");
}
void rollback() {
System.out.println("tx-rollback");
}
}
static class OrderServiceImpl implements OrderService {
@Override
public void outerSuccess() {
System.out.println("business-outerSuccess");
}
@Override
public void outerCallInner() {
System.out.println("business-outerCallInner");
innerFail();
}
@Override
public void innerFail() {
System.out.println("business-innerFail");
throw new RuntimeException("db error");
}
}
static class TransactionInvocationHandler implements InvocationHandler {
private final Object target;
private final TransactionManager txManager;
TransactionInvocationHandler(Object target, TransactionManager txManager) {
this.target = target;
this.txManager = txManager;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
txManager.begin();
try {
Object result = method.invoke(target, args);
txManager.commit();
return result;
} catch (Throwable e) {
txManager.rollback();
Throwable cause = e.getCause() != null ? e.getCause() : e;
throw cause;
}
}
}
public static void main(String[] args) {
OrderService target = new OrderServiceImpl();
OrderService proxy = (OrderService) Proxy.newProxyInstance(
OrderService.class.getClassLoader(),
new Class<?>[] {OrderService.class},
new TransactionInvocationHandler(target, new TransactionManager()));
proxy.outerSuccess();
try {
proxy.innerFail();
} catch (Exception e) {
System.out.println("directProxyFail=" + e.getMessage());
}
try {
proxy.outerCallInner();
} catch (Exception e) {
System.out.println("selfInvokeFail=" + e.getMessage());
}
}
}7.2 编译并运行
bash
javac SpringTransactionLikeDemo.java
java SpringTransactionLikeDemo7.3 你应该观察到什么
输出应包含这些关键信息:
text
tx-begin
business-outerSuccess
tx-commit
tx-begin
business-innerFail
tx-rollback
directProxyFail=db error
tx-begin
business-outerCallInner
business-innerFail
tx-rollback
selfInvokeFail=db error7.4 这个 demo 里最值得观察什么
这里最关键的不是“有没有回滚”,而是看调用路径:
proxy.innerFail():这是通过代理直接进入,事务增强明确生效proxy.outerCallInner():只有outerCallInner是通过代理进入,内部innerFail()不是再次经过代理,而是目标对象内部直接调用
这正是 Spring 事务里“自调用导致内部事务增强不生效”的根因基础。
8. 练习建议
下面这些练习做完,这一章会更扎实:
- 写一个事务成功与回滚示例
- 验证不同传播行为的差异
- 记录几个常见事务失效场景
- 用自己的话解释为什么事务和代理关系紧密
9. 自测问题
@Transactional为什么通常依赖代理?- 为什么有时抛异常却没有回滚?
- 事务传播行为在什么情况下最有价值?
- 为什么事务里不适合放长耗时逻辑?
- 自调用为什么可能让事务增强失效?
10. 自测核对要点
如果你的回答能覆盖下面这些点,说明这一章基本掌握到位了:
- Spring 声明式事务通常通过代理织入
- 回滚是否发生,取决于事务边界和异常传播方式
- 传播行为描述的是事务边界如何组合
- 自调用绕过代理,是事务失效高频原因
- 事务范围需要谨慎控制,避免把慢操作包进去