Springboot的@Transcational 事务会存在失效场景,要想了解SpringBoot的事务需要先了解一下SpringBoot的事务回滚是对哪些Exception有效的,从源码来看SpringBoot只会对RuntimeException进行回滚,除非指定了回滚类型为Exception,就能对所有的Exception进行回滚。

一、异常说明

异常的层次结构:

  • java.lang.Throwable
    • java.lang.Error
    • java.lang.Exception
      • java.lang.RuntimeException
      • 非RuntimeException

所有的异常都是由Throwable继承而来,然后在下一层立即分解为Error和Exception。
Error类层次结构描述了java运行时系统的内部错误和资源消耗错误。应用程序不应该抛出这种类型的对象。一般都是直接交由jvm处理,开发者不需要处理。Exception层次结构分为两个分支。一个分支派生于RuntimeException,另一个分支包含其他异常(即非RuntimeException)。 划分这两个分支的规则是:由程序错误导致的异常属于RuntimeException;而程序本身没问题,但由于I/O,网络等等导致的异常属于非RuntimeException。

	派生于RuntimeException的异常包含下面几种常见情况:
		1.错误的类型转换
		2.数组访问越界
		3.访问空指针
	派生于非RuntimeException的异常包含下面几种常见情况:
		1.试图在文件尾部后面读取数据
		2.试图打开一个错误格式的URL
		3.网络错误
		4.磁盘错误
		5.试图根据给定的字符串查找Class对象,而这个字符串表示的类并不存在。
		6.SQL异常

二、声明式事务失效原因

  • 1.异常回滚的异常不匹配。SpringBoot默认是对RuntimeException异常进行回滚,即不会对SQLException和IOException进行回滚;如果需要对SQLException和IOException进行回滚,则需要用@Transactional(rollbackFor=Exception.class)
    指定方法中抛出了Exception时都要进行回滚。像自定义异常如果是继承了Exception时,如果希望自定义异常也进行回滚则这里也要用@Transactional(rollbackFor=Exception.class)或@Transactional(rollbackFor={RuntimeException.class, 自定义异常})
  • 2.捕获了异常但不抛出去。如果异常被try{}catch{}了,事务就不回滚了,如果想让事务回滚必须再往外抛try{}catch{throw Exception}
  • 3.SpringBoot的声明式事务只对public方法有效,对非public方法无效,因为在SpringBoot类加载的时候private,protected方法没有被动态代理加载,无法使用声明式事务。
  • 4.多线程的情况下,子线程抛出exception,即使是在主线程方法加了@Transactional(rollbackFor=Exception.class)
    声明,主线程方法也不会进行回滚。或者主线程抛出了Exception,也不会回滚子线程的事务。因为多个线程不是在同一个事务之内的,已经是多个事务了。因此某一个线程发生异常,并不能让所有的线程都回滚。
  • 5.在service接口的方法A里面调用了用@Transcational声明B方法。如果方法A没加@Transcational声明,并且抛出了异常,这时候方法B的事务无法回滚。

例如:

 public class BussinessClassImpl implement BussinessClass {
 		// 在调用updateA的时候,updateB中出现了异常,则updateB已经执行的SQL不会回滚
        public void updateA(String s) {
            updateB(s);
        }
        
        @Transcational
        public void updateB(String s) {
            // 操作数据库
            // 模拟一个RuntimeException
            int i = 10/0;
        }
    }
  • 6.事务传播行为 propagation不支持事务,比如设置Propagation.NOT_SUPPORTED以非事务方式执行。
  • 7.方法被static,final修饰,这是因为这个方法没被代理,导致的事务失效。

三、解决办法

1.使用@Transactional的时候尽量加上rollbackFor=Exception.class,对所有的异常都进行回滚。
2.在加了@Transactional声明的方法中使用try{}catch{throw Exception}时要抛出Exception。
3.只在public方法加@Transactional,如果是私有方法private但是又希望事务生效的,则直接在调用方加@Transcational方法,这样被调用方的private私有方法会按照默认的事务传播行为TransactionDefinition.PROPAGATION_REQUIRED发现调用方有事务会自动加入调用方的事务。
4.多线程的情况,本身就是调用方的方法执行完了,然后新开一个线程去执行其他的耗时操作,新线程的事务不在调用方的事务影响也不大,如果考虑一定要全部都在事务中,则避免这种写法。
5.

 解决此问题需要在当前类中注入当前类的实例bean,通过当前类的实例去调用事务增强的updateB方法,或者直接将事务添加至updateA方法,如下图所示:
    //解决方法1:使用代理对象
    public class BussinessClassImpl implement BussinessClass {
        @Autowired
        private BussinessClass bussinessClass;
        public void updateA(String s) {
            // 使用当前类的实例bean的方法进行调用,这样即使updateA出现了异常,updateB的事务也能回滚。updateB的事务传播属性是默认的@Transactional(Propagation.REQUIRED)即TransactionDefinition.PROPAGATION_REQUIRED。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。 这里这样写是通过代理的方式让事务生效,如果不通过代理的方式事务是不生效的。
            bussinessClass.updateB(s);
        }
        
        @Transcational
        public void updateB(String s) {
            // 操作数据库
            // 模拟一个RuntimeException
            int i = 10/0;
        }
    }
    
    //解决方法2:使用代理对象
    public class BussinessClassImpl implement BussinessClass {
    	@Autowired
    	private ApplicationContext applicationContext;
    	
        public void updateA(String s) {
            // 通过代理对象调用的方式让updateB方法的事务生效。
            applicationContext.getBean(BussinessClass.class).updateB(s);
        }
        
        @Transcational
        public void updateB(String s) {
            // 操作数据库
            // 模拟一个RuntimeException
            int i = 10/0;
        }
    }
    
    // 解决方法3:在调用方加事务@Transcational
    public class BussinessClassImpl implement BussinessClass {
    	// 在调用updateB方法时,即使updateB出现了异常,但是因为这时候updateB的事务没写传播行为,但是updateA方法有传播行为,因默认的事务传播行为是TransactionDefinition.PROPAGATION_REQUIRED,当调用方法有事务传播行为时,被调用方法默认加入到调用方法的事务。updateA方法一定要加@Transcational。updateB的@Transcational有没有都没所有,实际上是无需写的,因为一个类中的updateA调用updateB的时候updateB的事务实际上是失效的,是没用的。
        @Transcational
        public void updateA(String s) {
            //updateA使用事务
            updateB(s);
        }
        
        @Transcational
        public void updateB(String s) {
            // 操作数据库
            // 模拟一个RuntimeException
            int i = 10/0;
        }
    }

6.正确理解事务的propagation事务传播行为,在调用方和被调用方的方法配置正确的事务传播行为。
7.一般来说,都很少在static和final方法做数据操作吧,如果有请换一种写法。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐