@Transactional
注解
@Transactional
可以在类或方法上使用,以指定方法或类中的所有方法应该在事务上下文中执行。
当 @Transactional
注解应用于类时,它将成为该类中所有公共方法的默认事务设置。当应用于方法时,它将覆盖类级别的事务设置(如果有的话)。
@Transactional
注解上的注解:
@Target({ElementType.TYPE, ElementType.METHOD})
:- 指定
@Transactional
注解可以应用于类(TYPE)或方法(METHOD)。
- 指定
@Retention(RetentionPolicy.RUNTIME)
:- 表示
@Transactional
注解信息在运行时可以通过反射机制获取。
- 表示
@Inherited
:- 表示
@Transactional
注解可以被继承,即如果一个类使用了这个注解,它的子类也会继承这个注解。
- 表示
@Documented
:- 表示
@Transactional
注解会被 javadoc 工具记录。
- 表示
@Reflective
:- 用于指示 AOT(Ahead-Of-Time)编译器生成必要的反射调用。
注解属性
value()
和transactionManager()
:- 这两个属性是别名,用于指定事务管理器的名称,允许不同的方法或类使用不同的
TransactionManager
。
- 这两个属性是别名,用于指定事务管理器的名称,允许不同的方法或类使用不同的
label()
:- 用来定义事务的标签,可以用于描述事务,并由事务管理器解释。
propagation()
:- 指定事务的传播行为。默认值为
Propagation.REQUIRED
,表示如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。
- 指定事务的传播行为。默认值为
isolation()
:- 指定事务的隔离级别。默认值为
Isolation.DEFAULT
,表示使用底层数据库的默认隔离级别。
- 指定事务的隔离级别。默认值为
timeout()
和timeoutString()
:- 指定事务的超时时间(以秒为单位)。
timeoutString()
允许使用字符串表达式,例如占位符。
- 指定事务的超时时间(以秒为单位)。
readOnly()
:- 指定事务是否只读。如果为
true
,则表示事务是只读的,可以由事务管理器进行优化。
- 指定事务是否只读。如果为
rollbackFor()
和rollbackForClassName()
:- 指定哪些异常类型应该触发事务回滚。
rollbackFor()
使用异常类,而rollbackForClassName()
使用异常类名的模式。
- 指定哪些异常类型应该触发事务回滚。
noRollbackFor()
和noRollbackForClassName()
:- 指定哪些异常类型不应该触发事务回滚。这两个属性的使用方式和
rollbackFor()
及rollbackForClassName()
相同。
- 指定哪些异常类型不应该触发事务回滚。这两个属性的使用方式和
常用属性rollbackFor
rollbackFor
是一个Class类型的数组,它可以接受零个或多个异常类型(必须是Throwable
的子类),指示在抛出这些异常类型时必须回滚事务。
默认情况下,如果方法执行过程中抛出了RuntimeException
(运行时异常)或Error
(错误),则Spring事务管理器会回滚当前事务。而如果是checked exceptions(受检异常,例如IOException
),则默认不会回滚事务。
如果想要改变默认的回滚行为,例如,让某些特定的受检异常也能触发事务回滚,就需要使用rollbackFor
属性来指定这些异常类型。
示例:
@Transactional(rollbackFor = {IOException.class, SQLException.class})
public void doWork() {
// 方法体
}
要让所有异常都触发事务回滚,可以配置 @Transactional
注解的 rollbackFor
属性,使其包含 Throwable.class
。这样配置后,无论是运行时异常(RuntimeException
)还是检查型异常(checked exceptions
),甚至是错误(Error
),都会触发事务回滚。
示例:
@Transactional(rollbackFor = Throwable.class)
public void doWork() {
// 方法体
}
事务传播机制
七种机制
Spring框架提供了对事务管理的支持,并且允许开发者定义事务的行为,特别是当一个事务方法调用另一个事务方法时应该如何处理。这就是所谓的“事务传播机制”。
Spring的事务传播行为是通过@Transactional
注解中的propagation
属性来配置的,默认情况下,这个属性的值是PROPAGATION_REQUIRED
。
PROPAGATION_REQUIRED (默认)
- 如果当前存在事务,则加入该事务;如果当前不存在事务,则创建一个新的事务。
PROPAGATION_SUPPORTS
- 如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续执行。
PROPAGATION_MANDATORY
- 如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。
PROPAGATION_REQUIRES_NEW
- 创建一个新的事务,如果当前存在事务,则挂起当前事务。
PROPAGATION_NOT_SUPPORTED
- 以非事务方式执行操作,并挂起当前事务(如果存在)。
PROPAGATION_NEVER
- 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED
- 如果当前存在事务,则执行一个嵌套事务;如果当前不存在事务,则其行为与
PROPAGATION_REQUIRED
相同。这个选项通常用于支持保存点的数据库。
- 如果当前存在事务,则执行一个嵌套事务;如果当前不存在事务,则其行为与
示例说明
假设我们有两个事务方法:methodA
和 methodB
。
methodA
标记为@Transactional(propagation = PROPAGATION_REQUIRED)
。methodB
标记为不同的传播行为。
methodA
调用不同传播行为的methodB
时的结果:
情况 | methodB 的传播行为 | 结果 |
---|---|---|
1 | PROPAGATION_REQUIRED | methodB 将加入methodA 的事务中。 |
2 | PROPAGATION_SUPPORTS | methodB 将加入methodA 的事务中。如果没有事务,则以非事务方式执行。 |
3 | PROPAGATION_MANDATORY | methodB 将加入methodA 的事务中。如果没有事务,则抛出异常。 |
4 | PROPAGATION_REQUIRES_NEW | methodB 将创建一个新的事务,并挂起methodA 的事务。即使methodB 内部发生异常并回滚,也不会影响methodA 的事务。 |
5 | PROPAGATION_NOT_SUPPORTED | methodB 将以非事务方式执行,并挂起methodA 的事务。如果methodB 发生异常,不会影响methodA 的事务。 |
6 | PROPAGATION_NEVER | methodB 将以非事务方式执行。如果当前存在事务,则抛出异常。 |
7 | PROPAGATION_NESTED | 如果当前存在事务,则执行一个嵌套事务。如果内部事务失败,它将回滚到嵌套事务的保存点,而不会影响外部事务。 |
原理实现
Spring事务传播机制是通过AOP(面向切面编程)和代理模式来实现的。当一个方法被@Transactional
注解标记时,Spring会在运行时动态地为该方法创建一个代理对象,并在代理对象中增加事务管理的相关逻辑。
Spring使用JDK动态代理或CGLIB来创建目标对象的代理。如果目标类实现了接口,则默认使用JDK动态代理;如果没有实现接口,则使用CGLIB代理。
PlatformTransactionManager
是Spring事务管理的核心接口,其实现类(如DataSourceTransactionManager
)负责事务的创建、提交和回滚。事务的启动和上下文管理都通过这个接口完成。
在事务开始之前,事务管理器会被用来创建一个新的事务或者加入当前的事务上下文。
Spring利用ThreadLocal
来保存事务状态信息。每个线程都拥有自己的ThreadLocal
副本,这使得事务信息可以在同一个线程内的不同方法之间共享。
TransactionSynchronizationManager
是一个工具类,它使用ThreadLocal
来绑定事务资源和其他同步数据到当前线程上。
当一个带有@Transactional
的方法被调用时,代理会根据配置的propagation
属性决定如何处理事务。
如果是REQUIRED
(默认),并且没有现有事务,那么会启动新事务;如果有现有事务,则加入该事务。
在代理方法调用结束时,会检查方法执行过程中是否有异常抛出。
如果没有异常,且事务没有被标记为只读或仅限查询,则会提交事务。
如果方法执行过程中抛出了异常,而这个异常类型是需要回滚的(可以通过@Transactional
的rollbackFor
属性指定),则会回滚事务。
对于NESTED
传播行为,Spring会在现有事务内开启一个保存点。这样子事务可以独立地提交或回滚而不影响外部事务,除非外部事务本身回滚了。
默认情况下,Spring只会对未检查异常(即继承自RuntimeException
的异常)和错误(Error
)进行回滚。对于检查型异常(即继承自Exception
但不是RuntimeException
的异常),需要显式配置rollbackFor
属性才会触发回滚。
事务不生效
在Spring框架中,事务管理是确保数据一致性和系统稳定性的关键。然而,许多开发者在实际应用中常常会遇到事务不生效或事务不回滚的问题。
访问权限问题
Spring的事务管理默认只对public方法生效。如果事务方法的访问权限设置为private、default或protected,Spring将无法提供事务功能。这是因为Spring AOP代理无法拦截这些非public方法。
解决方案:确保事务方法被声明为public。
final方法问题
在Spring框架中,当某个方法被声明为final
时,这表示该方法不希望被任何子类重写。这种做法在确保方法行为的一致性方面非常有用。然而,这在Spring事务管理中可能会导致一些问题。
Spring事务管理依赖于AOP(面向切面编程)机制,它通过JDK动态代理或CGlib库生成代理类,在这些代理类中实现事务功能。当一个方法被声明为final
时,代理类无法重写该方法以添加事务功能,因为final
方法不能被重写。这会导致事务管理在这些方法上失效。
同样,static
方法也存在类似的问题。由于static
方法是类级别的,它们不属于任何对象实例,因此也无法通过动态代理机制来添加事务功能。
解决方案:避免将需要事务管理的方法声明为final。
方法内部调用
当一个方法被标记为@Transactional
时,Spring会在运行时创建一个代理对象,这个代理对象会拦截方法的调用并添加事务逻辑。然而,当在同一个类的内部调用另一个标记了@Transactional
的方法时,这种拦截机制并不会生效,因为调用是在对象内部直接进行的,而不是通过代理对象。这会导致内部调用的事务方法不会生成事务。
- Spring AOP代理机制:Spring AOP通过代理对象来拦截方法调用。当外部调用事务方法时,实际上是调用了代理对象的方法,代理对象会添加事务逻辑。
- 内部调用:当在同一个类的内部调用另一个方法时,调用的是
this
对象的方法,而不是代理对象的方法。因此,事务切面不会被触发。
解决方案:
- 新增一个 Service 方法:将需要事务管理的代码移到一个新的方法中,并在新方法上添加
@Transactional
注解。 - 在 Service 类中注入自己:通过注入当前类的实例(即代理对象),在内部调用时使用代理对象的方法。
- 使用
AopContext.currentProxy()
:通过AopContext.currentProxy()
获取当前代理对象,并通过代理对象调用方法。需要在 Spring 配置中启用expose-proxy
属性。
下面是详细讲解:
- 新增一个 Service 方法
将需要事务执行的代码移到一个新的方法中,并在这个新方法上添加 @Transactional
注解。这样,外部调用这个新方法时,会通过代理对象调用,从而触发事务管理。
查看代码
@Service
public class MyService {
@Transactional
public void externalMethod() {
// 外部调用的方法
internalMethod();
}
@Transactional
private void internalMethod() {
// 需要事务管理的代码
}
}
在这种情况下,externalMethod
是外部调用的方法,它会通过代理对象调用 internalMethod
,从而触发事务管理。
- 在 Service 类中注入自己
通过注入当前类的实例(即代理对象),可以在内部调用时使用代理对象的方法,从而触发事务管理。
查看代码
@Service
public class MyService {
@Autowired
@Lazy
private MyService self;
public void externalMethod() {
// 外部调用的方法
self.internalMethod();
}
@Transactional
public void internalMethod() {
// 需要事务管理的代码
}
}
注意添加@Lazy
注解,不然在新版本的spring中会出现循环依赖的异常。
在这种情况下,self
是代理对象,调用 self.internalMethod()
会触发事务管理。
- 使用
AopContext.currentProxy()
通过 AopContext.currentProxy()
获取当前代理对象,并使用该代理对象调用方法。这种方法需要在 Spring 配置中启用 expose-proxy
属性。
在 Spring 配置文件或 Java 配置中启用 expose-proxy
属性:
@Configuration
@EnableAspectJAutoProxy(exposeProxy = true)
public class AppConfig {
// 其他配置
}
在服务类中使用 AopContext.currentProxy()
获取代理对象,并通过代理对象调用方法:
查看代码
@Service
public class MyService {
public void externalMethod() {
// 外部调用的方法
((MyService) AopContext.currentProxy()).internalMethod();
}
@Transactional
public void internalMethod() {
// 需要事务管理的代码
}
}
在这种情况下,AopContext.currentProxy()
返回当前的代理对象,通过代理对象调用 internalMethod
会触发事务管理。
未被Spring管理
使用Spring事务的前提是对象必须被Spring管理,通常通过@Service
等注解实现。
解决方案:确保Service类上添加了@Service
或其他相应的Spring注解。
多线程调用
Spring事务是通过数据库连接实现的,每个线程都有自己的数据库连接。在不同线程中执行的操作属于不同的事务。
解决方案:确保事务操作在同一个线程中执行。
不支持事务的存储引擎
使用不支持事务的存储引擎(如MySQL的MyISAM)会导致事务不生效。
解决方案:使用支持事务的存储引擎,如MySQL的InnoDB。
事务不回滚
错误的传播特性
事务传播特性设置错误可能导致事务不回滚。例如,Propagation.NEVER
表示不支持事务,如果有事务则会抛出异常。
解决方案:选择正确的事务传播特性,如REQUIRED
、REQUIRES_NEW
或NESTED
。
自己吞了异常
如果在事务方法中捕获了异常但没有抛出,Spring将无法执行回滚操作。
解决方案:确保捕获异常后重新抛出或标记异常以便回滚。
手动抛了别的异常
如果抛出的异常不是运行时异常,Spring默认不会回滚事务。
解决方案:使用@Transactional(rollbackFor = Exception.class)
指定回滚的异常类型。
自定义了回滚异常
如果自定义了回滚异常,需要确保Spring能够识别这些异常。
解决方案:在@Transactional
注解中指定自定义的回滚异常。
参考链接: