通过这一个帖子,我们会涉及到以下问题:

首先可能大家都会有一个疑问:Spring 事务是拿来做什么的?为什么要有 Spring 事务?没有 Spring 事务会发生什么?事务简单来讲,就是要么执行,要么都不执行的一组操作。比如说一个经典的例子 - 转账:

如果在转账的时候网络突然出现了问题,导致小明余额减少但是小红的余额没有增加,那么这笔钱的去向就莫名消失了。事务的特性可以总结为四个方面,就是我们常说的 ACID :原子性、一致性、隔离性、持久性。AID 是手段,C 是目的。只有保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。

当然这个问题的兜底方案在现在已经很成熟了,比如下面这条短信:

因为本人用的是苹果手机,苹果手机信号经常是不好的,所以偶尔也会影响到支付。正是因为有了 ACID 这四大护法,你的钱才能在互联网世界里安全流转,不会因为网络波动、系统故障而神秘消失。这也是为什么像微信支付这样的系统,即使在网络不好的情况下,也要通过短信告诉你最终结果——它必须给你一个确定性的答复,这正是事务精神的体现。下次当你收到"支付结果短信"时,不妨想想背后正是 ACID 在默默守护着你的每一分钱。
只要跟数据有交互,都可以有事务的应用。Spring 也是支持事务的,除此之外还有 MySQL 。Spring支持编程式事务管理与声明式事务管理。大致来讲,编程式事务管理就是通过注入 TransactionTemplate 与 TransactionManager 来实现,而声明式事务管理通过 @Transactional 注解实现。
@Transactional(propagation = Propagation.REQUIRED)
public void aMethod {
//...
}
在这里,我们使用到了 @Transactional 这个注解。propagation 属性配置的是事务的传播行为,默认值 REQUIRED 。其他常用的配置参数如下表所示:
| 属性名 | 说明 |
| propagation | 事务的传播行为,默认值为 REQUIRED |
| isolation | 事务的隔离级别,默认值采用 DEFAULT |
| timeout | 事务的超时时间,默认值为-1(不会超时)。如果超过该时间限制但事务还没有完成,则自动回滚事务 |
| readOnly | 指定事务是否为只读事务,默认值为 false |
| rollbackFor | 用于指定能够触发事务回滚的异常类型,并且可以指定多个异常类型 |
@Transactional 最推荐使用于方法上,但是需要注意的是该注解只能应用到 public 方法上,否则不生效。同时也可以在类与接口上使用。
@Transactional 的原理是什么呢? @Transactional 是 Spring 框架提供的一个“事务开关”。你把它加在一个方法上,就等于告诉 Spring:“我这个方法里的所有数据库操作,必须作为一个整体(事务)来执行,要么全部成功,要么失败就全部撤销(回滚)。” Spring 的实现原理是基于 AOP(面向切面编程),它通过“动态代理”技术在你不改动原有代码的情况下,自动为这个方法加上“开启事务”、“提交事务”和“回滚事务”的“外壳”。
Spring 该用什么技术来创建这个“代理外壳”?用 DefaultAopProxyFactory.createAopProxy() ,以下代码可以说明:
// 这段代码就像一个“代理工厂”的决策流程图
public AopProxy createAopProxy(AdvisedSupport config) {
// 条件判断:什么情况下用CGLib,否则用JDK动态代理
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
// ... 一些安全检查 ...
// 核心决策逻辑:
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config); // 使用JDK代理
}
return new ObjenesisCglibAopProxy(config); // 使用CGLib代理
}
else {
return new JdkDynamicAopProxy(config); // 默认使用JDK代理
}
}
如果目标类实现了接口,就优先使用 JDK 动态代理;如果目标类没有实现任何接口,就使用 CGLib 动态代理。
如果一个类或者一个类中的 public 方法上被标注 @Transactional 注解的话,Spring 容器就会在启动的时候为其创建一个代理类,在调用被 @Transactional 注解的 public 方法的时候,实际调用的是,TransactionInterceptor 类中的 invoke() 方法。这个方法的作用就是在目标方法之前开启事务,方法执行过程中如果遇到异常的时候回滚事务,方法调用完成之后提交事务。
所以正因为 Spring 事务是基于动态代理实现的,Spring 事务使用 this 调用是不生效的。因为只有通过代理对象的方法调用才会应用事务管理的相关规则,当使用 this 调用时,是直接绕过了 Spring 代理机制,不会启用事务。
invoke() 方法就像是一个“事务管家”。你每次调用这个方法,其实都是这个“管家”在替你操办,它会在方法执行前开启事务,方法成功后提交事务,一旦出错则回滚事务,确保数据安全。
那么回滚事务时,会根据什么样的规则进行回滚呢?在一般情况下,事务只有遇到运行时异常才会回滚,运行时异常就是 RunTimeException 的子类。Error 也会导致回滚,但是在检查型(Checked)异常时不会回滚。
最后来用一个表格来总结 @Transactional 的注意事项:
| 注意事项分类 | 关键要点 | 说明与原因 |
| ① 方法与类作用域 | 必须作用于 public 方法 | Spring AOP 代理默认只对公共方法进行拦截,private、protected 方法上的注解不生效。 |
| 所在类必须被 Spring 管理 | 类必须是 Spring 容器中的 Bean(如使用 @Component、@Service 等注解)。 | |
| ② 代理机制限制 | 避免同类内部方法调用 | 在同一个类中,方法A调用带有 @Transactional 注解的方法B,事务不会生效。因为这绕过了代理对象。 |
| 不推荐在接口上使用 | 在接口上声明事务注解,一旦使用 CGLib 代理(基于类),注解会被忽略。为保持一致性,推荐在具体类上使用。 | |
| ③ 属性配置 | 正确设置 rollbackFor | 默认只在抛出运行时异常和 Error 时回滚。若需在检查型异常时也回滚,必须手动指定,如 @Transactional(rollbackFor = Exception.class)。 |
| 理解 propagation 传播行为 | 正确设置事务的传播行为(如 REQUIRED, REQUIRES_NEW 等),否则可能导致事务未按预期启动或嵌套行为错误。 | |
| ④ 环境依赖 | 数据库支持事务 | 底层数据库存储引擎本身必须支持事务(如 MySQL 的 InnoDB)。如果使用 MyISAM 等不支持事务的引擎,事务注解无效。 |
那么 Spring 事务就会没有失效的情况吗?那是当然有的。这里还是通过一张表格进行总结,与上面这张表有重复的地方:
| 序号 | 失效场景 | 说明与原因 | 示例 |
| 1 | 未捕获异常 | 事务方法中发生异常,但被方法内的 try-catch 捕获并“吞掉”,未抛出到事务管理器,导致事务无法感知异常,因而不会回滚。 | @Transactional public void methodA() { try { // 数据库操作... } catch (Exception e) { // 捕获后没有重新抛出或手动回滚 e.printStackTrace(); } } |
| 2 | 非受检异常 | (默认回滚,不会失效) 这是Spring事务的默认行为:当抛出 RuntimeException(如NullPointerException)或其子类时,事务会自动回滚。 | @Transactional public void methodA() { // 数据库操作... throw new RuntimeException("发生错误"); // 事务会回滚 } |
| 3 | 事务传播属性设置不当 | 在复杂的嵌套方法调用中,若传播行为(如REQUIRES_NEW, NESTED)配置不当,可能导致内层事务不受外层事务控制,或新事务未正确开启。 | @Transactional(propagation = Propagation.REQUIRED) public void outer() { inner(); // 调用内层方法 } @Transactional(propagation = Propagation.REQUIRES_NEW) public void inner() { // 如果配置错误或调用方式不对,可能无法独立开启新事务 } |
| 4 | 多数据源的事务管理未配置 | 在多个数据源的项目中,若未为特定数据源正确配置对应的 PlatformTransactionManager,或者未使用 @Transactional 的 value 属性指定使用哪个事务管理器,事务会失效。 | // 配置了dataSourceA和dataSourceB,但只定义了一个全局事务管理器 // 当在方法上使用@Transactional,并期望操作dataSourceB时,可能因找不到对应的事务管理器而失效 |
| 5 | 跨方法调用事务问题 | 在一个事务方法内部,调用另一个本类中的非事务方法,而该非事务方法又去执行数据库操作,这些操作可能不在事务管理范围内。 | @Transactional public void methodA() { methodB(); // 调用本类的非事务方法 } public void methodB() { // 这里的数据库操作不受methodA的事务保护 } |
| 6 | 事务在非公开方法中失效 | Spring AOP 代理机制只能拦截 public 方法。将 @Transactional 注解在 private, protected 等方法上,事务将不会生效。 | @Transactional private void internalMethod() { // 这个私有方法上的事务注解是无效的 } |
想要将事务的管理行为抽象出来,然后在不同的平台去实现,这样我们可以保证提供给外部的行为不变,方便我们扩展。那么为什么我们需要使用接口呢?其实接口提供了一系列功能列表的约定,接口本身不提供功能,它只定义行为。但是谁要用,就要先实现我,遵守我的约定,然后再自己去实现我定义的要实现的功能。
Spring 事务的三个核心管理接口分别有:
-
PlatformTransactionManager:(平台)事务管理器,Spring 事务策略的核心。
-
TransactionDefinition:事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。
-
TransactionStatus:事务运行状态。
PlatformTransactionManager(事务管理器)是 Spring 事务的"总指挥"。它不亲自干活,而是定下规矩:怎么开始一个事务、怎么提交、怎么回滚。具体怎么实现,交给不同平台(如JDBC、Hibernate)的"小弟"去完成。
public interface PlatformTransactionManager {
// 1. 获取事务(开始干活)
TransactionStatus getTransaction(TransactionDefinition definition);
// 2. 提交事务(活干完了,确认收货)
void commit(TransactionStatus status);
// 3. 回滚事务(活干砸了,撤销所有操作)
void rollback(TransactionStatus status);
}
-
getTransaction:根据定义好的规则(TransactionDefinition)开启一个事务,并返回当前事务的状态凭据(TransactionStatus)。
-
commit/rollback:根据状态凭据,决定是完成交易还是撤销所有操作。
TransactionDefinition(事务定义)是事务的"任务说明书"。它详细规定了这次事务要怎么做:要不要独立完成?能读取别人的临时数据吗?最多等多久?等等。
public interface TransactionDefinition {
// 关键属性常量(就像任务说明书的选项)
int PROPAGATION_REQUIRED = 0; // 默认:有活一起干,没活自己干
int PROPAGATION_REQUIRES_NEW = 3; // 必须开新任务,不跟别人掺和
int ISOLATION_READ_COMMITTED = 2; // 只能读取别人已确认的数据
int TIMEOUT_DEFAULT = -1; // 不设超时,随便干多久
// 获取这些属性的方法
int getPropagationBehavior(); // 获取传播行为
int getIsolationLevel(); // 获取隔离级别
int getTimeout(); // 获取超时时间
boolean isReadOnly(); // 是否只读
}
TransactionStatus(事务状态)是事务的"实时状态监控器"。它能告诉你当前事务进行到哪一步了:是刚开始吗?有没有设置回滚点?是不是已经标记要回滚了?
public interface TransactionStatus {
boolean isNewTransaction(); // 是不是刚开的新事务?
boolean hasSavepoint(); // 有没有设置"安全存档点"?
void setRollbackOnly(); // 标记"这活干砸了,必须撤销"
boolean isRollbackOnly(); // 检查是否已被标记要回滚
boolean isCompleted(); // 活干完没有?(提交或回滚了)
}
在上面又提到了一个概念:传播行为。传播行为属于事务属性,而事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上,是在 TransactionDefinition 接口中定义的,有隔离级别、传播行为等。而事务属性又包含五个方面:

传播行为是事务属性之一,是为了解决业务层方法之间互调用的事务问题。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
举个例子:我们在 A 类的 aMethod() 方法中调用了 B 类的 bMethod() 方法。这个时候就涉及到业务层方法之间互相调用的事务问题。如果我们的 bMethod() 如果发生异常需要回滚,如何配置事务传播行为才能让 aMethod() 也跟着回滚呢?这就需要用到传播隔离级别的知识了。
事务传播行为解决的是"方法嵌套调用时,事务该怎么处理"的问题。当一个事务方法调用另一个事务方法时, Spring 需要知道这两个事务之间的关系该如何处理。
// 传播行为的7种类型
public enum Propagation {
REQUIRED, // 默认:有事务就加入,没有就新建
SUPPORTS, // 有事务就加入,没有就算了
MANDATORY, // 必须有事务,没有就报错
REQUIRES_NEW, // 不管有没有,我都要新建事务
NOT_SUPPORTED, // 非事务运行,有事务就挂起
NEVER, // 必须在非事务环境运行,有事务就报错
NESTED // 嵌套事务,有父事务就嵌套,没有就新建
}
事务隔离级别解决的是"多个事务同时操作数据时,互相能看到什么"的问题。它定义了事务之间的"可见性规则"。
public enum Isolation {
DEFAULT, // 用数据库默认的
READ_UNCOMMITTED, // 能读到别人未提交的数据
READ_COMMITTED, // 只能读到别人已提交的数据
REPEATABLE_READ, // 多次读取结果一致
SERIALIZABLE // 完全隔离,一个一个来
}
事务超时属性给事务设个"倒计时",超时自动放弃,防止事务卡死。
@Transactional(timeout = 30) // 30秒超时
事务只读属性告诉数据库"我只是看看,不修改",数据库会做优化,提升查询性能。
@Transactional(readOnly = true) // 只读事务
事务回滚规则定义什么情况下事务应该回滚,什么情况下应该提交。
// 默认:只有 RuntimeException 和 Error 才回滚
@Transactional
// 自定义:指定某些异常也回滚
@Transactional(rollbackFor = {MyException.class, IOException.class})
// 自定义:某些异常不回滚
@Transactional(noRollbackFor = {BusinessException.class})
1272

被折叠的 条评论
为什么被折叠?



