一、@Transactional注解简介
@Transactional是Spring框架中基于 AOP 的一个注解,用于在方法级别控制事务。这个注解告诉Spring框架在方法执行过程中,使用事务管理功能。如果该方法正常执行,则事务将被提交;如果方法抛出异常,则事务将被回滚。
二、@Transactional 工作原理
通过Spring AOP动态代理为标注了@Transactional注解的方法织入切面的增强代码逻辑并生成对应的代理对象,而切面的增强代码就是实现事务的相关操作,再通过这个代理对象去调用将目标方法以及切面中的事务处理逻辑。切面中的事务处理逻辑主要做了以下操作:
- 获取方法上标注的注解的元数据,包括传播行为、隔离级别、异常配置等信息。
- 通过ThreadLocal获取事务上下文,检查是否已经激活事务。
- 如果已经激活事务,则根据传播行为,看是否需要新建事务。
- 开启事务,先通过数据库连接池获取链接,关闭链接的autocommit,然后在try catch里反射执行真正的数据库操作,通过异常情况来决定是commit还是rollback。
下面使用伪代码简单描述下@Transactional注解标注的方法具体的执行逻辑。
@Transactional
public void updateUser() {
// 执行业务逻辑
doUpdate();
}
// 等价于
public void updateUser() {
//事务管理器-开启事务-拿到一个事务
transactionalManager.beginTranscation;
//关闭事务自动提交,需要手动提交
autoCommit = false;
public void invoke () {
try {
// 执行业务逻辑
doUpdate();
} cache(Exception e) {
// 异常回滚
Rollback();
}
//提交
commit();
}
}
三、@Transactional 常用属性
- propagation:定义事务的传播行为。
- isolation:定义事务的隔离级别。
- rollbackFor:用于指定哪些异常需要回滚事务。
- noRollbackFor:用于指定哪些异常不需要回滚事务。
- timeout:用于设置事务的超时时间,单位为毫秒。
四、事务的传播机制
事务传播行为是Spring框架中事务管理的一部分,它决定了在一个事务中调用另一个带有@Transactional注解的方法时,事务如何传播。下面是常用的几种传播行为:
(1)REQUIRED
如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。在大多数场景下,使用 REQUIRED 传播行为即可满足需求。
- 场景一:当前存在事务
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// 更新A表
updateA();
// 调用methodB方法
methodB();
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
// 更新B表
updateB();
}
当调用methodA时,会开启一个事务A。
在执行methodA时调用methodB方法时,methodB 不会开启新的事务,而是加入到事务A中。
这时两个方法在同一个事务A中,无论哪个方法出现异常,两个方法所做的操作都会回滚。
- 场景二:当前没有事务
public void methodA() {
// 更新A表
updateA();
// 调用methodB方法
methodB();
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
// 更新B表
updateB();
}
当调用methodA时,不会开启事务,在执行methodA时调用methodB方法时,methodB会开启一个新的事务。
如果methodB执行过程中出现异常,事务B会回滚对B表的更新操作。
但是由于methodA 没有起事务,A表的更新操作并不会回滚。
(2)SUPPORTS
如果当前存在事务,则加入该事务;如果当前没有事务,就以非事务方式执行。这种传播行为适用于不强制要求事务,但可以充分利用现有事务的场景。
- 场景一:当前存在事务
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// 更新A表
updateA();
// 调用methodB方法
methodB();
}
@Transactional(propagation = Propagation.SUPPORTS)
public void methodB() {
// 更新B表
updateB();
}
当调用methodA时,会开启一个新的事务A。
在执行methodA时调用methodB方法时,methodB 不会开启新的事务,而是加入到事务A中。
这时两个方法在同一个事务A中,无论哪个方法出现异常,两个方法所做的操作都会回滚。
- 场景二:当前没有事务
public void methodA() {
// 更新A表
updateA();
// 调用methodB方法
methodB();
}
@Transactional(propagation = Propagation.SUPPORTS)
public void methodB() {
// 更新B表
updateB();
}
当调用methodA时,不会开启事务,在执行methodA时调用methodB方法时,methodB也不会开启新的事务。
相当于methodB以普通方法执行,执行过程中出现任何异常都不会回滚。
(3)NOT_SUPPORTED
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。这种传播行为适用于不需要事务支持的场景。
- 场景一:当前存在事务
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// 更新A表
updateA();
// 调用methodB方法
methodB();
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void methodB() {
// 更新B表
updateB();
}
当调用methodA时,会开启一个事务A,在执行methodA时调用methodB方法,
不会开启新的事务也不会加入事务A。此时事务A会被挂起,直到methodB方法执行完成。
如果methodA执行出现异常,事务A会将methodA执行的操作回滚,methodB方法的执行不受影响。
- 场景二:当前没有事务
public void methodA() {
// 更新A表
updateA();
// 调用methodB方法
methodB();
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void methodB() {
// 更新B表
updateB();
}
当调用methodA时,不会开启事务,在执行methodA时调用methodB方法时,methodB也不会开启新的事务。
相当于methodA和methodB都以普通方法执行,执行过程中出现任何异常都不会回滚。
(4)REQUIRES_NEW
创建一个新事务,如果当前存在事务,把当前事务挂起,直到新事务完成。这种传播行为适用于需要独立于当前事务的场景。
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// 更新A表
updateA();
// 调用methodB方法
methodB();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// 更新B表
updateB();
}
当调用methodA时,会开启一个事务A,在执行methodA时调用methodB方法,会开启一个新的事务B。
此时事务A会被挂起,直到事务B执行完成。由于methodA和methodB分别在两个不同的事务中执行,
所以任何一个方法中出现异常只会触发当前方法所在事务的回滚操作。
不会影响另一个方法所在事务的回滚操作。
(5)MANDATORY
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。这种传播行为适用于必须在事务中执行的场景。
- 场景一:当前存在事务
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// 更新A表
updateA();
// 调用methodB方法
methodB();
}
@Transactional(propagation = Propagation.MANDATORY)
public void methodB() {
// 更新B表
updateB();
}
当调用methodA时,会开启一个事务A,
在执行methodA时调用methodB方法,不会开启新的事务,而是加入事务A。
这时两个方法在同一个事务A中,无论哪个方法出现异常,两个方法所做的操作都会回滚。
- 场景二:当前没有事务
public void methodA() {
// 更新A表
updateA();
// 调用methodB方法
methodB();
}
@Transactional(propagation = Propagation.MANDATORY)
public void methodB() {
// 更新B表
updateB();
}
当调用methodA时,不会开启事务,在执行methodA时调用methodB方法时,methodA会抛出异常,
methodB方法不会执行。也就是说methodB方法必须在存在事务的方法中调用。
(6)NEVER
必须以非事务方式执行,如果当前存在事务,则抛出异常。这种传播行为适用于不能在事务中执行的场景。
- 场景一:当前存在事务
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// 更新A表
updateA();
// 调用methodB方法
methodB();
}
@Transactional(propagation = Propagation.NEVER)
public void methodB() {
// 更新B表
updateB();
}
当调用methodA时,会开启一个事务A,在执行methodA时调用methodB方法,methodA会抛异常,methodB不会执行。也就是说methodB方法不能在存在事务的方法中调用。
- 场景二:当前没有事务
public void methodA() {
// 更新A表
updateA();
// 调用methodB方法
methodB();
}
@Transactional(propagation = Propagation.NEVER)
public void methodB() {
// 更新B表
updateB();
}
当调用methodA时,不会开启事务,在执行methodA时调用methodB方法时,
methodB也不会开启事务,methodB以普通方法执行。
(7)NESTED
如果当前存在事务,则以嵌套事务的方式执行;如果不存在事务,则创建一个新的事务。嵌套事务是一种特殊的事务,它可以独立于外部事务进行回滚,但提交时仍依赖于外部事务,并且。这种传播行为适用于需要在单个事务内执行多个独立操作的场景。
- 场景一:当前存在事务
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// 更新A表
updateA();
try {
// 调用methodB方法
methodB();
} catch {
}
}
@Transactional(propagation = Propagation.NESTED)
public void methodB() {
// 更新B表
updateB();
}
当调用methodA时,会开启一个事务A。
在执行methodA时调用methodB方法时,methodB 会开启新的嵌套事务A_B。
如果methodA执行过程出现异常,则methodA和methodB都会回滚。
如果methodB执行过程出现异常,methodB方法会回滚,
如果methodA通过try捕获了methodB的异常,则methodA能正常执行完,不会回滚。
如果methodA没有捕获了methodB的异常,methodA也会回滚。
- 场景二:当前没有事务
public void methodA() {
// 更新A表
updateA();
// 调用methodB方法
methodB();
}
@Transactional(propagation = Propagation.NESTED)
public void methodB() {
// 更新B表
updateB();
}
当调用methodA时,不会开启事务,在执行methodA时调用methodB方法时,methodB会开启一个新的事务。
如果methodB执行过程中出现异常,事务B会回滚对B表的更新操作。
但是由于methodA 没有起事务,A表的更新操作并不会回滚。
五、事务的隔离级别
- ISOLATION_DEFAULT:使用底层数据库的默认隔离级别。是spring默认设置。
- ISOLATION_READ_UNCOMMITTED:可以读取其他事务未提交的数据。这是最低的隔离级别,可能会导致脏数据问题。
- ISOLATION_READ_COMMITTED:只能读取其他事务已提交的数据,可以解决脏读;但也能读取到在当前事务开始之后提交的数据,可能会出现不可重复读的问题。
- ISOLATION_REPEATABLE_READ:可以重复读取当前事务开始之前其他事务已提交的数据,之后其他事务提交的数据读取不到,可以避免脏读以及不可重复读的问题,但是可能会出现幻读的问题。
- ISOLATION_SERIALIZABLE:完全序列化。这是最高的隔离级别,能确保在同一时刻,只能有一个事务访问数据库。
六、@Transactional失效场景
- 非公有方法:注解标注方法修饰符为非public时,事务会失效。因为@Transactional是基于动态代理实现的,如果被@Transactional标注的方法的修饰符不是public的话,将不会对该bean进行代理对象创建或者不会对方法进行代理调用。从而导致增强的事务操作的代码没有被执行,事务也就不会生效。
- final 修饰的方法:如果动态代理使用的是cglib实现的话,由于cglib实现动态代理的方式主要是靠运行期动态创建目标类的子类,从而重写目标方法,将增强代码织入进来。而被final 修饰的方法是无法被重写的,所以会导致增强代码也无法被执行,事务也就不会生效。
- 类内部访问:如果普通方法中调用的@Transactional标注的方法位于同一个类中,并且是通过this关键字直接调用的,事务不会生效。首先这个普通方法不会被AOP代理拦截,因此不会生成事务。而通过this关键字直接内部调用没有经过代理对象,无法将事务的增强代码织入到@Transactional标注的方法方法中,所以被普通方法调用的@Transactional标注的方法事务也就无法生效。
- 异常不匹配:@Transactional 注解默认处理运行时异常,即只有抛出运行时异常时,才会触发事务回滚,否则并不会回滚。如果通过rollbackFor属性指定了具体的回滚异常时,实际抛的异常与指定的异常不一致时,事务也不会生效。还有就是如果抛出的异常被捕获了,事务也不会生效。
- 跨线程传播:由于Spring AOP的代理是在运行时动态生成的,而跨线程调用时,Spring AOP无法动态生成代理对象,从而无法执行增强代码,事务也就无法生效。