• 周二. 10 月 8th, 2024

5G编程聚合网

5G时代下一个聚合的编程学习网

热门标签

@Transaction中的rollbackFor属性一定需要声明出来吗?

admin

11 月 28, 2021

前言

​ 在阿里巴巴的内部JAVA开发规范手册中,方法【function】需要在Transactional注解指定rollbackFor或者在方法中显示的rollback。一开始不知什么用意,只知道只要给方法
添加@Transaction注解,如果出现了异常,spring会将该方法涉及的sql增删改操作进行回滚。今天特意找了资料解惑。

疑问?

@Transaction中的rollbackFor属性一定需要声明出来吗?

异常的分类

先来看看异常的分类

​ 只有继承了Throwable类的异常,才能够被Java虚拟机识别并抛出。

The {@code Throwable} class is the superclass of all errors and
* exceptions in the Java language. Only objects that are instances of this
* class (or one of its subclasses) are thrown by the Java Virtual Machine or
* can be thrown by the Java {@code throw} statement. Similarly, only
* this class or one of its subclasses can be the argument type in a
* {@code catch} clause.

Throwable下分为ExceptionError,对于Spring实现的事务框架而言,如果方法抛出了Error类型的错误是支持回滚的。但是对于Exception则不完全相同。

Exception再分类

​ Exception作为异常,又可细分为运行时异常RuntimeException非运行时异常

像上图中罗列的运行时异常等:

  • NullPointerException:空指针异常
  • ArithmeticException:算数运算异常
  • ClassCastException:类型强制转换异常
  • IndexOutOfBoundsException:下标越界异常
  • NumberFormatException:数字格式异常

非运行时异常等:

  • IOException:IO异常
  • SQLException:SQL异常

Exception 再再分类

  • 可检查的异常(checked Exception):Exception下除了RuntimeException外的异常
  • 不可检查的异常(unchecked Exception):RuntimeException及其子类和错误(Error)

对于不可检查的异常而言

如果不对不可检查的异常进行处理,那么出现运行时异常之后,要么是线程中止,要么是主程序终止

如果不想终止,则必须捕获所有的不可检查的异常,决不让这个处理线程退出。队列里面出现异常数据了,正常的处理应该是把异常数据舍弃,然后记录日志。不应该由于异常数据而影响下面对正常数据的处理。

示例:

@Override
@Transactional
public void save(EditExamPaperReq req)  {
        TExamPaper tExamPaper = VOUtil.from(req,TExamPaper.class);
        examPaperDao.insert(tExamPaper);
    	int a = 1 / 0;
        throw new RuntimeException();
}

​ 在上面的示例中,我没有指定rollbackFor属性,不管我是产生了什么运行时异常,只要产生的是不可检查的异常,那么spring的事务框架肯定就会帮我把该方法中涉及到的DML语句进行回滚。不相信的读者可以自行试验。

对于可检查的异常而言

​ 对于可检查的异常,比如IOException,java编译器会强制要求我们对相关代码进行try{}catch{}操作,再将捕捉到的异常是抛出还是自行处理。

示例一:

@Override
@Transactional(rollbackFor = Exception.class)
public void save(EditExamPaperReq req) throws Exception {
        TExamPaper tExamPaper = VOUtil.from(req,TExamPaper.class);
        examPaperDao.insert(tExamPaper);
    try {
        throw new Exception();
    } catch (Exception e) {
        e.printStackTrace();
        throw e;
    }
}

在上面的代码中,如果我们在catch中没有将异常抛出throw e,意味着对这块代码进行了自定义处理; 那么即使声明了rollbackFor = Exception.class,那对于spring提供的事务框架来说,它没有拦截到Exception,所以不会将insert语句进行回滚。

示例二:

@Override
@Transactional(rollbackFor = ServiceException.class)
public void save(EditExamPaperReq req) throws Exception {
        TExamPaper tExamPaper = VOUtil.from(req,TExamPaper.class);
        examPaperDao.insert(tExamPaper);
    try {
        throw new Exception();
    } catch (Exception e) {
        e.printStackTrace();
        throw e;
    }
}
public class ServiceException extends RuntimeException {

上面例子rollbackFor拦截的是自定义的RuntimeException,意味着告诉spring,指定它要拦截的是ServieException异常,除此之外的异常,都不进行处理。即使catch代码块抛出的是可检查的Exception异常,但spring事务框架也不会帮该方法的insert操作进行回滚。

总结

spring的事务框架会根据rollbackFor属性对指定的异常进行拦截处理:

  • 当没有只当rollbackFor属性时,spring事务框架需要拦截所有的向外抛出的异常
  • 当指定了rollbackFor属性时,spring事务框架只会对属性中指定的异常进行拦截
/**
 * Defines zero (0) or more exception {@link Class classes}, which must be
 * subclasses of {@link Throwable}, indicating which exception types must cause
 * a transaction rollback.
 * <p>By default, a transaction will be rolling back on {@link RuntimeException}
 * and {@link Error} but not on checked exceptions (business exceptions). See
 * {@link org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)}
 * for a detailed explanation.
 * <p>This is the preferred way to construct a rollback rule (in contrast to
 * {@link #rollbackForClassName}), matching the exception class and its subclasses.
 * <p>Similar to {@link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Class clazz)}.
 * @see #rollbackForClassName
 * @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
 */
Class<? extends Throwable>[] rollbackFor() default {};

查看rollbackFor的文档说明,该属性值默认是一个空数组,可以声明0个或多个Exception类。之所以有这个属性,是为了能够对指定的Exception类进行拦截。也就是说是一个拦截异常的规则而已。并需要一定要在使用@Transaction注解时添加进来!

发表回复