Spring事务 失效 以及 不回滚或者 回滚过多 的场景及解决办法

Spring 事务的失效场景
Spring 事务的本质 是通过 注解@Transactional 来实现的
注:简述一下 实现原理,@Transactional 是通过 Spring 的AOP 来实现事务的 AOP 是通过反射机制得到 动态代理(Proxy) 通过代理对象 进行的事务操作

  1. 访问权限 造成 Spring 事务失效
    • java的访问权限主要是:private、default、protected、public,它们的权限则是依次变大
    • 查看源码,spring事务的实现AbstractFallbackTransactionAttributeSource类的 computeTransactionAttribute方法中有个判断,如果目标方法不是public,则TransactionAttribute返回null,即不支持事务
    • 在这里插入图片描述
  2. 方法被final 修饰
    • 方法被final 修饰过的 方法 是不允许被子类继承的。
    • 同样 因为Spring 事务底层实现使用了代理 aop 通过jdk的动态代理或者cglib,生产的代理类,在代理类中实现了事务功能,如果方法被final修饰,无法重写该方法,也就是无法添加事务功能
    • 如果方法变成了静态的方法 ,静态方法属于类的 并非属于对象的 因此也不能被重写 不能实现事务。
public class TestFinalService {

    //Methods annotated with '@Transactional' must be overridable
    @Transactional
    public final void select(){

    }
    //Methods annotated with '@Transactional' must be overridable 
    @Transactional
    public static void selectTesy(){

    }
}
  1. 方法内部调用
    • 由于spring的事务实现是因为aop生成代理,这样是直接调用了this对象,没有通过代理去调用,spring 事务也不会生效
    • 解决办法:
      1. 可以在自己内部注入自己 然后调用
      2. 创建两个类 方法分别放到两个对象中
      3. 通过AopContentent
public class TestFinalService {
    @Autowired
    public TestFinalService testFinalService;

    @Transactional
    public  void select(){
        save();
        testFinalService.save();
        TestFinalService testFinalService = (TestFinalService) AopContext.currentProxy();
        testFinalService.save();
    }
    @Transactional
    public  void save(){

    }
}

  1. 类没有被Spring 进行管理

    • 这个很好理解 事务本身就是要交给Spring 管理 类没有交给spring管理 自然 spring 事务也无法管理该方法的事务
    • 使用spring事务的时候,对象要被spring进行管理,也就是需要创建bean,一般我们都会加@Controller、@Service、@Component、@Repository等注解,可以自动实现bean实例化和依赖注入的功能。,如果忘记加了,也会导致,事务的失效
  2. 多线程调用 操作同一组数据

    • select调用了事务方法save,但是事务方法在另一个线程里面调用,这样会导致两个方法在不同的一个线程中,获取的数据库连接也不一样,所以会是两个不同的事务,如果save的方法抛出了异常,query回滚是不可能的,看过spring源码,我们可以知道,spring的事务是通过连接数据库来实现的,当前线程保存了一个map,key—数据源,value----数据库连接,事务其实就是指向同一个连接的,只有拥有同一个数据库连接才能同时提交和回滚,如果在不同的线程,数据库的连接不是同一个,所以事务也不是同一个。
@Service
public class TestFinalService {
    @Autowired
    public TestFinalService testFinalService;

    @Transactional
    public  void select(){
        new Thread(()->{
            testFinalService.save();
        }).start();
    }
    @Transactional
    public  void save(){

    }
}

  1. 设计的表不支持事务
    • mysql5之前默认的数据库引擎是MyISAM,可能一些老的项目还在使用,但是他是不支持事务的
  2. 上下文参数未开启事务
    - 如果创建的不是springboot项目可能会导致这样的问题出现,因为springboot项目有自动装配的类DataSourceTransactionManagerAutoConfiguration,已经默认开启了事务,配置spring.datasource参数就行,如果是spring项目,需要在applicationContext.xml配置的,不然事务不会生效
<!-- 配置事务管理器 --> 
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager"> 
    <property name="dataSource" ref="dataSource"></property> 
</bean> 
<tx:advice id="advice" transaction-manager="transactionManager"> 
    <tx:attributes> 
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes> 
</tx:advice> 
<!-- 用切点把事务切进去 --> 
<aop:config> 
    <aop:pointcut expression="execution(* com.demo.*.*(..))" id="pointcut"/> 
    <aop:advisor advice-ref="advice" pointcut-ref="pointcut"/> 
</aop:config> 

  1. 错误的事务传播
    - 我们在使用@Transactional注解时,是可以指定propagation参数的。该参数的作用是指定事务的传播特性,spring目前支持7种传播特性:
    1. REQUIRED 如果当前上下文中存在事务,那么加入该事务,如果不存在事务,创建一个事务,这是默认的传播属性值。
    2. SUPPORTS 如果当前上下文存在事务,则支持事务加入事务,如果不存在事务,则使用非事务的方式执行。
    3. MANDATORY 如果当前上下文中存在事务,否则抛出异常。
    4. REQUIRES_NEW 每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。
    5. NOT_SUPPORTED 如果当前上下文中存在事务,则挂起当前事务,然后新的方法在没有事务的环境中执行。
    6. NEVER 如果当前上下文中存在事务,则抛出异常,否则在无事务环境上执行代码。
    7. NESTED 如果当前上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。
- 如果我们在手动设置propagation参数的时候,把传播特性设置错了
@Service
public class DemoService {

    @Transactional(propagation = Propagation.NEVER)
    public  void query(Demo demo) {

    }
}

  1. 自己捕获异常
    • 事务不回滚,可能是我们在写代码的时候自己在代码手动进行了try…catch
    • 这种情况下,spring事务不会进行回滚,因为我们进行了手动捕获异常,然后没有手动抛出,如果想要spring事务的正常回滚,必须抛出它能处理的异常,如果没有抛出异常,spring会认为程序没有问题。
@Transactional
    public  void query(Demo demo) {
        try {
            save(demo);
        } catch (Exception e) {
            System.out.println("异常");
        }
    }

  1. 手动抛出了异常
    • 我们没有手动捕获异常,但是如果抛出的异常不正确,spring事务也不会回滚。
    • 我们捕获了异常,然后手动抛出Exception,事务同样不会回滚,因为spring事务,默认情况下不会回滚Exception(非运行时的异常),只会回滚RuntimeException(运行时异常)和Error(错误)。
 @Transactional
    public  void query(Demo demo) throws Exception{
        try {
            save(demo);
        } catch (Exception e) {
            throw new Exception(e);
        }
    }

  1. 自定义异常回滚
    • 在使用@Transactional注解声明事务时,有时我们想自定义回滚的异常,spring也是支持的。可以通过设置rollbackFor参数,来完成这个功能。
    • 我们自定义的业务异常,如果在执行上面这段代码,保存和更新数据时,程序报错了,抛了SqlException、DuplicateKeyException等异常。而BusinessException是我们自定义的异常,报错的异常不属于BusinessException,所以事务也不会回滚。即使rollbackFor有默认值,但阿里巴巴开发者规范中,还是要求开发者重新指定该参数。
      因为如果使用默认值,一旦程序抛出了Exception,事务不会回滚,这会出现很大的bug。所以,建议一般情况下,将该参数设置成:Exception或Throwable。
  @Transactional(rollbackFor = BusinessException.class)
    public  void query(Demo demo) throws Exception{
            save(demo);
    }
  1. 嵌套事务回滚过头
@Service
public class DemoService {

    @Autowired
    private DemoTwoService demoTwoService;

    @Autowired
    private DemoDao demoDao;
    public
    @Transactional(rollbackFor = Exception.class)
    public  void query(Demo demo) throws Exception{
        demoDao.save(demo);
        demoTwoService.save(demo);
    }
}


  • 原本只是希望回滚demoServise.save(),不回滚demoDao.save(demo);,但是这种情况两个都会被回滚,因为demoTwoService.save(demo);没有捕获异常,往上抛出,导致query进行回滚,所以同时回滚了两个
  • 解决办法:
@Service
public class DemoService {

    @Autowired
    private DemoTwoService demoTwoService;

    @Autowired
    private DemoDao demoDao;
    public
    @Transactional(rollbackFor = Exception.class)
    public  void query(Demo demo) throws Exception{
        demoDao.save(demo);
        try {
            demoTwoService.save(demo);
        } finally {
            
        }
    }
}


  • 可以将内部嵌套事务放在try/catch中,并且不继续往上抛异常。这样就能保证,如果内部嵌套事务中出现异常,只回滚内部事务,而不影响外部事务。
  1. 编程式响应事务
    - 上面的事务失效都是基于@Transactional注解的,我们把这种事务叫做:声明式事务。其实,spring还提供了另外一种创建事务的方式,即通过手动编写代码实现的事务,我们把这种事务叫做:编程式事务。
@Service
public class DemoService {
    

    @Autowired
    private TransactionTemplate transactionTemplate;

    @Transactional(rollbackFor = Exception.class)
    public  void query(Demo demo) throws Exception{
       transactionTemplate.execute((status -> {
           save(demo);
           return Boolean.TRUE;
       }));
    }

    @Transactional
    public void save(Demo demo) {

    }
}


在spring中为了支持编程式事务,专门提供了一个类:TransactionTemplate,在它的execute方法中,就实现了事务的功能。
使用TransactionTemplate的编程式事务可以

  • 避免由于spring aop问题,导致事务失效的问题。
  • 能够更小粒度的控制事务的范围。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值