在现代软件开发中,数据的一致性和完整性是至关重要的。为了保证这些特性,Spring框架提供了强大的事务管理机制,让开发者能够更加自信地处理数据库操作。然而,事务并非银弹,存在一些失效的情景,本文将带您深入探究Spring事务及其失效场景,并为您呈现应对策略。
spring事务失效的场景
@Transactional概述
在Spring Boot中,@Transactional是一个用于声明式事务管理的注解。事务是一种用来维护数据库操作的一致性和隔离性的机制,确保在一组操作中,要么所有操作都成功提交,要么所有操作都回滚,以保持数据的完整性。@Transactional注解让你能够在方法级别上定义事务的行为,而无需显式编写事务管理代码。
@Transactional注解的详细解释:
-
标记事务起点: 将
@Transactional
注解放在方法上,表示该方法应该在一个事务内执行。当方法被调用时,Spring会自动开始一个事务。 -
事务属性:
@Transactional
注解支持多个属性,用于配置事务的各个方面。一些常用的属性包括:isolation
:指定事务的隔离级别,定义了事务之间的可见性。例如,Isolation.READ_COMMITTED
表示读已提交的隔离级别。propagation
:定义了事务的传播行为,即方法被另一个事务方法调用时如何处理事务。例如,Propagation.REQUIRED
表示如果已存在事务,则沿用该事务,否则创建一个新的事务。readOnly
:指定事务是否只读。如果只读,可以优化事务处理,因为不需要考虑写操作。timeout
:定义事务的超时时间,超过该时间未提交则自动回滚。rollbackFor
和noRollbackFor
:指定在哪些异常情况下回滚事务或不回滚事务。
-
嵌套事务: 如果在方法中调用了另一个被
@Transactional
注解标记的方法,那么默认情况下会共享外部方法的事务。如果希望内部方法有自己的事务,可以使用Propagation.REQUIRES_NEW
传播行为。 -
回滚策略: 默认情况下,Spring会将未捕获的运行时异常(
RuntimeException
及其子类)作为触发事务回滚的标志。你也可以通过rollbackFor
属性指定哪些异常触发回滚。 -
应用范围:
@Transactional
注解可以用于类级别(作用于所有方法)或方法级别(作用于单个方法),具体取决于你的需求。 -
生效方式:
@Transactional
注解依赖于Spring的AOP(面向切面编程)技术,它会在运行时通过代理机制来拦截方法调用,并根据注解的配置来管理事务。
TransactionSynchronizationManager辅助打印事务
在Spring Boot中,你可以使用TransactionSynchronizationManager来获取有关已开启事务的信息。TransactionSynchronizationManager是Spring的一个工具类,用于管理和监视事务的状态。要打印已开启的事务,你可以通过以下步骤进行:
- 导入必要的类: 首先,确保你的类中导入了TransactionSynchronizationManager类。
import org.springframework.transaction.support.TransactionSynchronizationManager;
- 打印当前事务名称或者状态
boolean isTransactionActive = TransactionSynchronizationManager.isActualTransactionActive();
if (isTransactionActive) {
System.out.println("An active transaction is present.");
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());
} else {
System.out.println("No active transaction.");
}
失效场景详解
- 未被Spring管理的对象
未被标记为@Component、@Service、@Repository或其他Spring管理注解的普通Java类不会被Spring自动管理。
import org.springframework.transaction.annotation.Transactional;
public class NoManageService {
/**
import org.springframework.transaction.annotation.Transactional;
public class NoManageService {
/**
* 事务不生效场景:未被Spring管理的对象
*/
@Transactional
public void doHandle() {
// 一通处理
}
}
}
- 异常未被正确捕获和抛出
异常被捕获但未重新抛出,事务将不会回滚。
/**
* 事务不生效场景:异常未被正确捕获和抛出
*/
@Transactional(rollbackFor = Exception.class)
public void method1(){
try {
//一通处理猛如虎
}catch (Exception e){
//异常只吞不吐不回滚
}
}
- 在非公共方法上使用@Transactional
/**
* 事务不生效场景:在非公共方法上使用@Transactional
*/
@Transactional(rollbackFor = Exception.class)
private void method2(){
//一通处理猛如虎
}
- 事务方法内部调用
当事务方法内部通过普通的方法调用,而不是通过Spring管理的Bean调用其他带有@Transactional注解的方法时,事务可能失效。这是因为Spring的事务机制是基于代理实现的,只有通过代理对象调用的方法才能被AOP拦截并应用事务。
/**
* 事务不生效场景:事务方法内部调用,未被AOP拦截
*/
public void method3(){
method5();
}
@Resource
private TestService testService;
/**
* 事务生效,使用自我引用的方式调用: 如果在同一个类中需要在事务内部调用其他事务方法,
* 可以通过自我引用的方式来调用,以便事务代理可以生效。
*/
public void method4(){
testService.method5();
}
@Transactional(rollbackFor = Exception.class)
public void method5(){
//一通处理猛如虎
}
- 事务传播机制配置错误
如果在方法中调用了另一个被@Transactional
注解标记的方法,那么默认情况下会共享外部方法的事务。如果希望内部方法有自己的事务,可以使用Propagation.REQUIRES_NEW
传播行为。
/**
* method6中调用method7,会共享method6方法的事务
*/
@Transactional(rollbackFor = Exception.class)
public void method6(){
//获取当前事务名称,若未开启事务,则为null
String tranName = TransactionSynchronizationManager.getCurrentTransactionName();
log.info("method6事务名称=======》{}",tranName);
//一通处理猛如虎
testService.method7();
}
@Transactional(rollbackFor = Exception.class)
public void method7(){
//获取当前事务名称,若未开启事务,则为null
String tranName = TransactionSynchronizationManager.getCurrentTransactionName();
log.info("method7事务名称=======》{}",tranName);
//一通处理猛如虎
}
调用method6则会打印
23:04:48.861 [http-nio-8180-exec-1] INFO c.x.t.TestServiceImpl - [method6,69] - method6事务名称=======》cn.xj.transactional.TestServiceImpl.method6
23:04:48.862 [http-nio-8180-exec-1] INFO c.x.t.TestServiceImpl - [method7,77] - method7事务名称=======》cn.xj.transactional.TestServiceImpl.method6
/**
* method8中调用method9,method9会重新开启个事务
*/
@Transactional(rollbackFor = Exception.class)
public void method8(){
//获取当前事务名称,若未开启事务,则为null
String tranName = TransactionSynchronizationManager.getCurrentTransactionName();
log.info("method8事务名称=======》{}",tranName);
//一通处理猛如虎
testService.method9();
}
@Transactional(rollbackFor = Exception.class,propagation= Propagation.REQUIRES_NEW)
public void method9(){
//获取当前事务名称,若未开启事务,则为null
String tranName = TransactionSynchronizationManager.getCurrentTransactionName();
log.info("method9事务名称=======》{}",tranName);
//一通处理猛如虎
}
调用method8则会打印
23:12:51.058 [http-nio-8180-exec-1] INFO c.x.t.TestServiceImpl - [method8,93] - method8事务名称=======》cn.xj.transactional.TestServiceImpl.method8
23:12:51.086 [http-nio-8180-exec-1] INFO c.x.t.TestServiceImpl - [method9,101] - method9事务名称=======》cn.xj.transactional.TestServiceImpl.method9
总结
Spring事务为我们提供了一个强大的工具来维护数据的一致性和完整性。然而,在使用过程中,了解事务失效的场景以及应对策略同样重要。通过合理配置事务传播行为、异常处理以及使用注解管理事务,我们能够更好地应对各种情况,确保系统的稳定性和可靠性。