前言
在阿里巴巴的内部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
下分为Exception
和Error
,对于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注解时添加进来!