6

@Transactional注解真的有必要声明rollbackFor属性吗?

 2 years ago
source link: https://blog.51cto.com/u_15773567/5745890
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

今天在看spring的事务底层源码时,想到一个问题,@Transactional注解真的有必要声明rollbackFor属性吗?因为之前有许多资料,包括公司的java编码规范上也有提及到这一点。

不知道读者们有没想过这个问题,但我看完源码后,个人觉得是有必要的。见解不到位的话,希望读者能指明。

**异常:**如下图所示,我们都知道Exception分为运行时异常RuntimeException和非运行时异常(检查时异常)。

@Transactional注解真的有必要声明rollbackFor属性吗?_sql

那么spring默认会对如上的哪些异常进行回滚呢?

**答案:**RuntimeException、Error.

spring源码如下说明:

​ spring在执行方法抛出异常后,会调用​​completeTransactionAfterThrowing​​方法,也在该方法中会去判断并执行是回滚还是提交操作。

protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
if (logger.isTraceEnabled()) {
logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
"] after exception: " + ex);
}

// transactionAttribute的实现类为RuleBasedTransactionAttribute,父类为DefaultTransactionAttribute
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
try {
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by rollback exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by rollback exception", ex);
throw ex2;
}
}
else {
// We don't roll back on this exception.
// Will still roll back if TransactionStatus.isRollbackOnly() is true.
try {
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by commit exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by commit exception", ex);
throw ex2;
}
}
}

我们看到这个if分支进行分析,如果该if满足,则会进行回滚。

if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex))

查看txInfo.transactionAttribute.rollbackOn(ex)方法,其中的​​this.rollbackRules​​就是我们在@Transactional注解上配置的rollbackFor属性,

public boolean rollbackOn(Throwable ex) {
RollbackRuleAttribute winner = null;
int deepest = Integer.MAX_VALUE;

if (this.rollbackRules != null) {
// 遍历所有的RollbackRuleAttribute,判断现在抛出的异常ex是否匹配RollbackRuleAttribute中指定的异常类型的子类或本身
for (RollbackRuleAttribute rule : this.rollbackRules) {
int depth = rule.getDepth(ex);
if (depth >= 0 && depth < deepest) {
deepest = depth;
winner = rule;
}
}
}

// User superclass behavior (rollback on unchecked) if no rule matches.
if (winner == null) {
return super.rollbackOn(ex);
}

// ex所匹配的RollbackRuleAttribute,可能是NoRollbackRuleAttribute,如果是匹配的NoRollbackRuleAttribute,那就表示现在这个异常ex不用回滚
return !(winner instanceof NoRollbackRuleAttribute);
}

我这里简单配了个ServiceException。如下图

@Transactional注解真的有必要声明rollbackFor属性吗?_spring_02

**根据一:**在rollbackFor属性元素遍历时,会根据​​getDepth​​方法去找抛出的异常,是不是就是我们声明的rollbackFor属性中的异常,如果是,判断是不是​​NoRollbackRuleAttribute​​类型,是的话就不回滚,否则就回滚。

再看​​getDepth​​方法,会根据抛出的异常,判断异常名字是否跟我们声明的异常是否相同,相同则返回,不同则递归抛出异常的父类,直到遍历到​​Throwable.class​​顶类。

private int getDepth(Class<?> exceptionClass, int depth) {
if (exceptionClass.getName().contains(this.exceptionName)) {
// Found it!
return depth;
}
// If we've gone as far as we can go and haven't found it...
if (exceptionClass == Throwable.class) {
return -1;
}
return getDepth(exceptionClass.getSuperclass(), depth + 1);
}

**根据二:**如果​​getDepth​​找不到对应的异常类,就从默认实现类​​DefaultTransactionAttribute.rollbackOn(Throwalbe x)​​方法进行判断。

@Override
public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error);
}

​DefaultTransactionAttribute​​判断抛出的异常是​​RuntimeException​​或者​​Error​​就会进行回滚。说明spring没有对非运行时异常(检查时异常)进行处理,这是因为非运行时异常在编码时,是需要我们开发人员手动去进行try catch进行处理的,也不允许抛出非运行时异常,比如IOException,不然编译器编译都不通过,更别谈运行程序了。

**根据三:**除了IOException等需要try catch的异常,还有一些不需要try catch处理但是也不属于RuntimeException的异常,比如:SQLException,尽管JdbcTemplate会将sqlException转为​​BadSqlGrammarException​​,具体可以参考​​translateException(String task, @Nullable String sql, SQLException ex)​​方法,也可以亲自试一下。

​Exception in thread "main" org.springframework.jdbc.BadSqlGrammarException: StatementCallback; bad SQL grammar [insert into user(username) value ('hyza', 'xx')]; nested exception is java.sql.SQLException: Column count doesn't match value count at row 1​

 除了SQLException,可能还有其它Exception的非RuntimeException的子类,但是不确定是否也会跟jdbcTemplate一样转为RuntimeException类型,所以@Transactional注解指定rollbackFor=Exception.calss确实是万全之策。

**总结:**尽管spring默认帮我们回滚RuntimeException和Error的异常事务,但@Transactional注解指定rollbackFor=Exception.calss确实是万全之策。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK