Spring事务失效的各种情况

Spring事务失效的各种情况

Spring 提供了两种事务管理方式:

  • 声明式事务管理
  • 编程式事务管理

对不同的持久层访问技术,编程式事务提供一致的事务编程风格,通过模板化的操作一致性地管理事务;
而声明式事务基于 Spring AOP 实现,却并不需要程序开发者成为 AOP 专家,亦可轻易使用 Spring 的声明式事务管理。
一、编程式事务管理控制事务的失效场景:

我们需要在代码中显式调用 begin()、commit()、rollback()等事务管理相关的方法,这就是编程式事务管理。
简单地说,编程式事务就是在代码中显式调用开启事务、提交事务、回滚事务的相关方法,因此代码侵入性较大。

示例:

@Autowired
private PlatformTransactionManager transactionManager;

public void transactionDemo() {
    
    
    TransactionStatus transactionStatus = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
    try {
    
    
          // TODO 业务代码
			...
          // 提交事务
          this.transactionManager.commit(transactionStatus);
    } catch (Exception e) {
    
    
          // 回滚事务
          this.transactionManager.rollback(transactionStatus);
    }
}

二、Spring的声明式事务@Transactional注解控制事务的失效场景:

  1. 如果你的数据库引擎不支持事务,再怎么使用事务注解也是没用的,就像MySQL的MyISAM引擎是没有事务的。

改成 InnoDB 引擎则支持事务。

  1. 没有被Spring管理
    就像这个:如果注释了@Service注解,它就没有被Spring管理了,不会被加载成为Bean,事务自然也失效了
// @Service
public class TestService Impl implements TestService {
    
    

    @Transactional
    public void test(Obj o) {
    
    
        // update o
    }
    
}
  1. 方法不是public的

这是Spring官方文档的话

When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.

就是 @Transactional 只能用于 public 的方法上,否则事务不会失效,如果要用在非 public 方法上,可以开启 AspectJ 代理模式

保存的方法设置为private,这样spring无法进行代理。spring代理主要两种方式:

  • 第一种是jdk动态代理,面向接口,无法代理private方法。
  • 第二种是cglib方式,这个是以子类方式实现,由于方法设置为private导致这里无法进行代理而事务失效。
  1. 自身调用问题与两个互相调用问题
@Service
public class TestServiceImpl implements TestService {
    
    

    public void update(Obj o) {
    
    
        updateObj(o);
    }
    @Transactional
    public void updateObj(Obj o) {
    
    
        // update o
    }
}

update方法上面没有加 @Transactional 注解,调用有 @Transactional 注解的 updateObj 方法,updateObj 方法上的事务管用吗?
再来看下面的例子:

@Service
public class OrderServiceImpl implements OrderService {
    
    

    @Transactional
    public void update(Order order) {
    
    
        updateOrder(order);
    }
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void updateOrder(Order order) {
    
    
        // update order
    }
}

这次在 update 方法上加了 @Transactional,updateOrder 加了 REQUIRES_NEW 新开启一个事务,那么新开的事务管用么?

这两个例子的答案是:不管用!

因为它们发生了自身调用,就调该类自己的方法,而没有经过 Spring 的代理类,默认只有在外部调用事务才会生效。

被Spring的AOP增强的类,在同一个类的内部方法调用时,其被调用方法上的增强通知将不起作用。换句话说, Spring的事务传播策略在内部方法调用时将不起作用。

解决办法:

  • 1、将该类的所有方法都加上事务,即所有方法都会被代理,这样方法B事务才会生效。
  • 2、调用时使用cglib生成的bean去调用方法B,比如说
public class ServiceA{
    
    
...
	public void A(){
    
     
	        serviceA.B()
	}
}

而不是直接使用this.B();
这个的解决方案就是在的类中注入自己,用注入的对象再调用另外一个方法,这个不太优雅,另外一个可行的方案可以参考《Spring 如何在一个事务中开启另一个事务?》这篇文章。

不同类之间调用:

不同类之间方法调用时,异常发生在无事务的方法中,但不是被调用的方法产生的,被调用的方法的事务无效。只有异常发生在开启事务的方法内,事务才有效

  1. 数据源没有配置事务管理器
 @Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
    
    
    return new DataSourceTransactionManager(dataSource);
}

如上面所示,当前数据源若没有配置事务管理器,那也是白搭!

  1. 自己选择不支持事务
@Service
public class OrderServiceImpl implements OrderService {
    
    

    @Transactional
    public void update(Order order) {
    
    
        updateOrder(order);
    }
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void updateOrder(Order order) {
    
    
        // update order
    }
}

Propagation.NOT_SUPPORTED: 表示不以事务运行,当前若存在事务则挂起,详细的可以参考《事务隔离级别和传播机制》这篇文章。
都主动不支持以事务方式运行了,那事务生效也是白搭!

  1. 异常被你自己吃了
// @Service
public class OrderServiceImpl implements OrderService {
    
    

    @Transactional
    public void updateOrder(Order order) {
    
    
        try {
    
    
            // update order
        } catch {
    
    

        }
    }
}

Spring 事务是使用 AOP 环绕通知和异常通知,就是对方法进行拦截,在方法执行前开启事务,在捕获到异常时进行事务回滚,在方法执行完成后提交事务。

在方法内抓了异常,但是没有往外抛,所以就没有办法回滚了。
当然这里可以使用手动事务,就可以解决了

  1. 抛出异常类型错误
    上面的例子再抛出一个异常:
// @Service
public class OrderServiceImpl implements OrderService {
    
    

    @Transactional
    public void updateOrder(Order order) {
    
    
        try {
    
    
            // update order
        } catch {
    
    
            throw new Exception("更新错误");
        }
    }
    
}

这样事务也是不生效的,因为默认回滚的是:RuntimeException,如果你想触发其他异常的回滚,需要在注解上配置一下,如:

@Transactional(rollbackFor = Exception.class)

这个配置仅限于 Throwable 异常类及其子类。

Spring 团队建议在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。

在接口上使用 @Transactional 注解,只能当你设置了基于接口的代理时它才生效。因为注解是不能继承的,这就意味着如果正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装。

Spring 文档中写到:Spring AOP 部分使用 JDK 动态代理或者 CGLIB 来为目标对象创建代理,如果被代理的目标对象实现了至少一个接口,则会使用 JDK 动态代理。
所有该目标类型实现的接口都将被代理。若该目标对象没有实现任何接口,则创建一个CGLIB代理。

持续更新中…

参考:
https://segmentfault.com/a/1190000021510031
https://mp.weixin.qq.com/s/RTEMPBB6AFmmdj0uw1SDsg
https://mp.weixin.qq.com/s/1TEBnmWynN4nwc6Q-oZfvw
对这些情况添加自己的看法

猜你喜欢

转载自blog.csdn.net/qq_41257365/article/details/106676212