【注解@Transactional 的事务回滚测试】
通常,在项目中可能看到注解@Transactional(rollbackFor=Exception.class),如果类加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚(也可以细分颗粒度,到需要事务监控的方法上添加此注解而非类上)。
在@Transactional注解中如果不配置rollbackFor属性,那么事物只会在遇到RuntimeException的时候才会回滚,加上rollbackFor=Exception.class,可以让事物在遇到非运行时异常时也回滚
默认spring事务只在发生未被捕获的 RuntimeExcetpion 时才回滚。也可以说spring会对unchecked异常进行事务回滚;如果是checked异常则不回滚。
一般将派生于Error或者RuntimeException的异常称为unchecked异常,所有其他的异常成为checked异常。
那么我是不是可以理解为 不加注解,发生空指针而不捕获时会发生回滚?(下面进行验证)
PS: @Transactional注解 应当应用于service层实现类或实现类中的public方法上,且如果为只读(查询)方法无需添加注解。
PSS:着重测试注解属性:rollbackFor :用于指定能够触发事务回滚的异常类型,可以指定多个异常类型,常见的runtime异常:空指针异常,数组越界异常
现将进行测试,测试的控制变量如下:
1.添加注解@Transactional(rollbackFor = Exception.class)
2.添加注解@Transactional(norollbackFor = Exception.class)
3.捕获 runtime 异常
4.不捕获 runtime 异常
好了,开测:
测试用例1:不添加@Transactional注解,测试异常为空指针异常,而捕获的是runtime异常,结果是 无回滚,无异常抛出,数据顺利更新。
测试用例2:不添加@Transactional注解,测试异常为空指针异常,而捕获的是数组越界异常,结果是 无回滚,抛出空指针异常,数据可顺利更新。
测试用例3:和测试用例2类似,但没有使用try-catch去捕获runtime异常,结果依旧是 没有回滚。
测试用例4:添加@Transactional(rollbackFor = NullPointerException.class)注解,指定空指针类型异常,测试异常为空指针异常,而捕获的是数组越界异常,结果是 发生回滚, 数据无更新。
测试用例5:添加@Transactional(rollbackFor = NullPointerException.class)注解,测试异常为空指针异常,而捕获的是Exception异常(包含空指针异常),结果是 不发生回滚, 数据有更新。
测试用例6:添加@Transactional(norollbackFor = NullPointerException.class)注解,测试异常为空指针异常,而捕获的ArrayIndexOutOfBoundsException
异常(包含空指针异常),结果是 不发生回滚, 数据有更新。
好了,以上列举了6种情况,总结如下:
1.当没有配置注解@Transactional时,spring默认是不开启事物管理的。
2.配置注解后,回滚的前提是,try-catch捕获情况符合注解中参数的规范,且方法用public修饰,要求mysql数据库的引擎是支持事物的,比如InnoDB( 可以用show variables like 'default_storage_engine'语句来查看数据库当前引擎)
3.配置注解后,如果catch中捕获到了对应异常,那么即使配置注解也不会发生回滚。
3.再次声明,unchecked exception–编译器不要求强制处置的异常,可以理解为 java.lang.RuntimeException类及它的子类都是非受查异常,如错误的类型转换异常:ClassCastException,下标越界异常:ArrayIndexOutOfBoundsException,空指针访问异常:NullPointerException,除零溢出异常:ArithmeticException等;
4.注解属性:
【关于事务嵌套】
有两个事务方法,父方法中调用子方法,想实现子方法回滚而父方法不触发回滚,可以设置如下:
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW) // 子方法触发事务时会暂时挂起父事务;
【关于同一类中非事务方法A调用 事务方法B,事务失效问题】
// 因为 B方法被A调用,而事务需要切面实现,这时对B方法的切入失效,我们需要新建一个代理对象去调用方法B,这样事务不会失效。(XxxxService为业务类对象) XxxxxxService self = (XxxxxxxService) AopContext.currentProxy();
需要依赖:
<!-- 切点表达式依赖 --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.5</version> <scope>runtime</scope> </dependency>
附:
参考博客:https://www.cnblogs.com/clwydjgs/p/9317849.html
spring aop 异常捕获原理:被拦截的方法需显式抛出异常,并不能经任何处理,这样aop代理才能捕获到方法的异常,才能进行回滚,默认情况下aop只捕获runtimeexception的异常,但可以通过配置来捕获特定的异常并回滚。
在Spring FrameWork 的事务框架中推荐的事务回滚方法是,在当前执行的事务上下文中抛出一个异常。如果异常未被处理,当抛出异常调用堆栈的时候,Spring FrameWork 的事务框架代码将捕获任何未处理的异常,然后并决定是否将此事务标记为回滚。
在默认配置中,Spring FrameWork 的事务框架代码只会将出现runtime, unchecked 异常的事务标记为回滚;也就是说事务中抛出的异常时RuntimeException或者是其子类,这样事务才会回滚(默认情况下Error也会导致事务回滚)。在默认配置的情况下,所有的 checked 异常都不会引起事务回滚。
注:Unchecked Exception包括Error与RuntimeException. RuntimeException的所有子类也都属于此类。另一类就是checked Exception。
Spring的事务管理默认是针对unchecked exception回滚,也就是默认对Error异常和RuntimeException异常以及其子类进行事务回滚,且必须对抛出异常,若使用try-catch对其异常捕获则不会进行回滚!(Error异常和RuntimeException异常抛出时不需要方法调用throws或try-catch语句);
checked异常,checked异常必须由try-catch语句包含或者由方法throws抛出,且事务默认对checked异常不进行回滚。
在service层若不加@Transactional则spring默认是不开启事物管理的。
从源码来看,默认回滚的 类型确实是runtime异常和error