探究Spring事务:了解失效场景及应对策略

2023-08-25 11:44:12 浏览数 (1)

在现代软件开发中,数据的一致性和完整性是至关重要的。为了保证这些特性,Spring框架提供了强大的事务管理机制,让开发者能够更加自信地处理数据库操作。然而,事务并非银弹,存在一些失效的情景,本文将带您深入探究Spring事务及其失效场景,并为您呈现应对策略。

spring事务失效的场景

_20230804232601.png

@Transactional概述

在Spring Boot中,@Transactional是一个用于声明式事务管理的注解。事务是一种用来维护数据库操作的一致性和隔离性的机制,确保在一组操作中,要么所有操作都成功提交,要么所有操作都回滚,以保持数据的完整性。@Transactional注解让你能够在方法级别上定义事务的行为,而无需显式编写事务管理代码。

@Transactional注解的详细解释:

  • 标记事务起点:@Transactional注解放在方法上,表示该方法应该在一个事务内执行。当方法被调用时,Spring会自动开始一个事务。
  • 事务属性: @Transactional注解支持多个属性,用于配置事务的各个方面。一些常用的属性包括:
    • isolation:指定事务的隔离级别,定义了事务之间的可见性。例如,Isolation.READ_COMMITTED表示读已提交的隔离级别。
    • propagation:定义了事务的传播行为,即方法被另一个事务方法调用时如何处理事务。例如,Propagation.REQUIRED表示如果已存在事务,则沿用该事务,否则创建一个新的事务。
    • readOnly:指定事务是否只读。如果只读,可以优化事务处理,因为不需要考虑写操作。
    • timeout:定义事务的超时时间,超过该时间未提交则自动回滚。
    • rollbackFornoRollbackFor:指定在哪些异常情况下回滚事务或不回滚事务。
  • 嵌套事务: 如果在方法中调用了另一个被@Transactional注解标记的方法,那么默认情况下会共享外部方法的事务。如果希望内部方法有自己的事务,可以使用Propagation.REQUIRES_NEW传播行为。
  • 回滚策略: 默认情况下,Spring会将未捕获的运行时异常(RuntimeException及其子类)作为触发事务回滚的标志。你也可以通过rollbackFor属性指定哪些异常触发回滚。
  • 应用范围: @Transactional注解可以用于类级别(作用于所有方法)或方法级别(作用于单个方法),具体取决于你的需求。
  • 生效方式: @Transactional注解依赖于Spring的AOP(面向切面编程)技术,它会在运行时通过代理机制来拦截方法调用,并根据注解的配置来管理事务。

TransactionSynchronizationManager辅助打印事务

在Spring Boot中,你可以使用TransactionSynchronizationManager来获取有关已开启事务的信息。TransactionSynchronizationManager是Spring的一个工具类,用于管理和监视事务的状态。要打印已开启的事务,你可以通过以下步骤进行:

  • 导入必要的类:首先,确保你的类中导入了TransactionSynchronizationManager类。
代码语言:javascript复制
import org.springframework.transaction.support.TransactionSynchronizationManager;
  • 打印当前事务名称或者状态
代码语言:javascript复制
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自动管理。

代码语言:javascript复制
import org.springframework.transaction.annotation.Transactional;

public class NoManageService {
/**
import org.springframework.transaction.annotation.Transactional;

public class NoManageService {
    /**
     * 事务不生效场景:未被Spring管理的对象
     */
    @Transactional
    public void doHandle() {
        // 一通处理
    }
}

}

  • 异常未被正确捕获和抛出

异常被捕获但未重新抛出,事务将不会回滚。

代码语言:javascript复制
    /**
     * 事务不生效场景:异常未被正确捕获和抛出
     */
    @Transactional(rollbackFor = Exception.class)
    public void method1(){
        try {
            //一通处理猛如虎
        }catch (Exception e){
            //异常只吞不吐不回滚
        }
    }
  • 在非公共方法上使用@Transactional
代码语言:javascript复制
    /**
     * 事务不生效场景:在非公共方法上使用@Transactional
     */
    @Transactional(rollbackFor = Exception.class)
    private void method2(){
        //一通处理猛如虎
    }
  • 事务方法内部调用

当事务方法内部通过普通的方法调用,而不是通过Spring管理的Bean调用其他带有@Transactional注解的方法时,事务可能失效。这是因为Spring的事务机制是基于代理实现的,只有通过代理对象调用的方法才能被AOP拦截并应用事务。

代码语言:javascript复制
    /**
     * 事务不生效场景:事务方法内部调用,未被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传播行为。

代码语言:javascript复制
    /**
     * 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则会打印

代码语言:javascript复制
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
代码语言:javascript复制
    /**
     * 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则会打印

代码语言:javascript复制
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事务为我们提供了一个强大的工具来维护数据的一致性和完整性。然而,在使用过程中,了解事务失效的场景以及应对策略同样重要。通过合理配置事务传播行为、异常处理以及使用注解管理事务,我们能够更好地应对各种情况,确保系统的稳定性和可靠性。

0 人点赞