一、事务的隔离级别
在Springboot应用中,如果我们想实现方法一旦执行有异常产生,就触发事务回滚,可以在方法上面添加@Transactional注解。如果应用采用mysql数据库,虽然mysql本身也有事务隔离机制,但在Sping+数据库的应用中,会以Spring事务为准。mysql定义的事务隔离级别为可重复读,在使用 Spring Boot 和 MySQL 的组合时,如果你不特别指定隔离级别,那么实际使用的将是 MySQL 的默认值 REPEATABLE READ。如果在一些特定场景中不想使用可重复读,可通过@Transactional注解的isolation属性来指定。isolation支持的选项有:
- ISOLATION_DEFAULT:使用后端数据库默认的隔离级别。
- ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、不可重复读或幻读。
- ISOLATION_READ_COMMITTED:允许读取已经提交的数据,可以防止脏读,但可能会出现不可重复读或幻读。
- ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果是一致的,除非数据是被当前事务本身所修改,可以防止脏读和不可重复读,但幻读仍可能发生。
- ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从 ACID 的隔离级别,所有的事务依次逐个执行,可以防止脏读、不可重复读以及幻读。
使用示例:
@Transactional(isolation = Isolation.READ_COMMITTED)
public void performTransaction() {
// 业务逻辑代码
}
二、事务的传播行为
事务的传播行为是指当一个事务方法被另一个事务方法调用时,两者之间的事务应该如何关联。通过配置不同的传播行为,可以控制是否应该创建新的事务、加入现有事务或者以非事务方式执行等。
Spring 提供了七种标准的事务传播行为,它们可以通过 @Transactional 注解的 propagation 属性来指定。以下是这些传播行为的详细说明:
PROPAGATION_REQUIRED (默认):
如果当前存在事务,则加入该事务;如果不存在,则创建一个新的事务。
这是最常用的传播行为,适用于大多数场景。
PROPAGATION_SUPPORTS
如果当前存在事务,则加入该事务;如果不存在,则以非事务方式执行。
适合那些对事务性没有特别要求的操作,如查询操作。
PROPAGATION_MANDATORY:
如果当前存在事务,则加入该事务;如果不存在,则抛出异常(IllegalTransactionStateException)。
用于强制要求在已有事务中执行的方法。
PROPAGATION_REQUIRES_NEW:
创建一个新的事务,如果当前存在事务,则将当前事务挂起。
适用于需要独立于外部事务执行的业务逻辑,确保内部操作不会影响外部事务的结果。
PROPAGATION_NOT_SUPPORTED:
以非事务方式执行操作,如果当前存在事务,则将当前事务挂起。
适合那些明确不需要事务的操作,如读取系统配置或发送邮件等。
PROPAGATION_NEVER:
以非事务方式执行,如果当前存在事务,则抛出异常(IllegalTransactionStateException)。
用于严格禁止在事务环境中执行的方法。
PROPAGATION_NESTED:
如果当前存在事务,则在嵌套事务内执行;如果不存在,则创建一个新的事务。
嵌套事务是外部事务的一部分,但可以独立于外部事务进行提交或回滚。这种传播行为依赖于底层数据库和驱动的支持,例如 MySQL 的 InnoDB 引擎支持保存点(SAVEPOINT),从而实现嵌套事务。
注意事项
性能考虑:选择合适的传播行为对于性能优化非常重要。例如,PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NOT_SUPPORTED 都会涉及到事务的挂起和恢复,这可能会带来额外的开销。
事务边界:正确理解事务的边界以及传播行为的影响,有助于避免潜在的问题,如死锁、数据不一致等。
嵌套事务支持:不是所有的数据库都支持嵌套事务。使用 PROPAGATION_NESTED 时,请确保你的数据库和驱动程序支持这一特性。
根据应用的具体需求选择适当的传播行为,可以帮助你更好地管理事务,确保数据的一致性和完整性。
三、Spring事务中存在的坑
在同一个类里面,编写两个方法,内部调用的时候,会导致事务设置失效。原因是没有用到
代理对象的缘故。具体来说:
Spring 使用 AOP 来实现事务管理,它会为每个带有 @Transactional 注解的方法创建一个代理对象。当你通过 Spring 容器获取这个类的实例并调用其方法时,实际上是调用了代理对象的方法,而不是原始类的方法。代理对象负责在方法调用前后插入事务管理逻辑。
然而,当你在一个类的非静态方法中直接调用另一个 @Transactional 方法时,调用并没有经过代理对象,而是直接调用了原始类的方法。因此,事务管理逻辑不会被应用,导致事务设置失效。
方法1:
1)、导入spring-boot-starter-aop依赖
2)、启动类添加注解@EnableAspectJAutoProxy(exposeProxy=true)
3)、事务使用的地方使用AopContext.currentProxy() 调用方法。
使用示例:
import org.springframework.aop.framework.AopContext;
@Service
public class MyService {
@Transactional
public void transactionalMethod() {
// 事务逻辑
}
public void performOperation() {
// 业务逻辑
((MyService) AopContext.currentProxy()).transactionalMethod();
}
}
不过这种方式使得代码更加复杂且不直观,因此尽量避免使用,除非绝对必要。
最推荐的做法是将事务方法移到不同的类中。这样可以确保每次调用事务方法时都通过代理对象进行,从而保证事务管理生效。具体可参考方法2:
方法2:
@Service
public class MyService {
@Autowired
private AnotherService anotherService;
public void performOperation() {
// 业务逻辑
anotherService.transactionalMethod();
}
}
@Service
public class AnotherService {
@Transactional
public void transactionalMethod() {
// 事务逻辑
}
}
拓展:@Transactional支持的配置属性大盘点
除了上面提到的propagation和isolation,@Transactional 注解里边还支持配置以下属性:
1. value 或 transactionManager
- 作用:指定要使用的事务管理器的名称。如果你的应用程序中有多个事务管理器(例如,针对不同的数据源),你可以使用这个属性来明确指定哪一个事务管理器应该管理当前方法的事务。
- 默认值:
"transactionManager"
,这是 Spring 默认的事务管理器 Bean 名称。
@Transactional("myTransactionManager")
public void myTransactionalMethod() { // 业务逻辑 }
2. readOnly
- 作用:指定事务是否为只读事务。只读事务通常用于查询操作,可以提高性能(例如,禁用脏页写入等)。
- 取值:
false
(默认):事务不是只读的,允许进行插入、更新和删除操作。true
:事务是只读的,仅允许查询操作。
@Transactional(readOnly = true)
public List<Entity> findAllEntities() { // 查询操作 }
3. timeout
作用:定义事务的超时时间(单位为秒)。如果事务在指定时间内未能完成,Spring 会自动回滚事务。
默认值:-1,表示使用后端数据库或事务管理器的默认超时设置。
@Transactional(timeout = 30)
public void longRunningOperation() { // 长时间运行的业务逻辑 }
4. rollbackFor
作用:指定哪些异常应该触发事务回滚。默认情况下,只有未检查异常(如 RuntimeException 及其子类)会触发回滚。你可以通过这个属性指定其他异常类型也应触发回滚。
取值:一个或多个异常类,可以用逗号分隔。
@Transactional(rollbackFor = {CustomCheckedException.class, AnotherException.class}) public void methodThatMayThrowExceptions() { // 业务逻辑 }
5. noRollbackFor
作用:指定哪些异常不应该触发事务回滚。默认情况下,所有未检查异常都会触发回滚,但你可以通过这个属性指定某些异常不应触发回滚。
取值:一个或多个异常类,可以用逗号分隔。
@Transactional(noRollbackFor = CustomCheckedException.class)
public void methodThatMayThrowCustomException() { // 业务逻辑 }
6. validation
作用:指定是否在事务开始之前验证事务属性。如果设置为 true,Spring 会在事务开始前检查事务属性是否符合要求,如果不符则抛出异常。
默认值:false,即不进行验证。
@Transactional(validation = true)
public void validateTransactionalAttributes() { // 业务逻辑 }