纠正互联网上关于捕获异常事务可提交的言论


互联网上流传着这么一句'定律':如果事务中通过try...catch...捕获异常,事务可正常提交.

此篇文章,我们验证下它的严谨性

【场景一】

@Transactional(rollbackFor = Exception.class)
public void updateOrderStatus() {
	try {
	  	update order set status = 6 where id=1;
  		int i = 3 / 0;
	} catch (Exception ignored) {}
}

【场景二】

@Transactional(rollbackFor = Exception.class)
public void updateOrderStatus() {
	try {
	  	update order set status = 6 where id=1;
  		service.calculate();	
	} catch (Exception ignored) {}
}

public void calculate() {
	int i = 3 / 0;
}


【场景三】

@Transactional(rollbackFor = Exception.class)
public void updateOrderStatus() {
	try {
	  	update order set status = 6 where id=1;
  		service.calculate();	
	} catch (Exception ignored) {}
}

@Transactional(rollbackFor = Exception.class)
public void calculate() {
	int i = 3 / 0;
}

假设 updateOrderStatus 和 calculate 方法的事务均生效,不考虑事务不生效的情况.


那么以上3种场景,方法 updateOrderStatus 执行结束之后,updateOrderStatus 方法是否能正常提交事务呢?

对于场景一和场景二来说,updateOrderStatus 方法可以正常提交,读者朋友应该不会有异议


接下来主要分析场景三的情况.
@Transactional(rollbackFor = Exception.class)
public void updateOrderStatus() {
	try {
	  	update order set status = 6 where id=1;
  		service.calculate();	
	} catch (Exception ignored) {}
}

@Transactional(rollbackFor = Exception.class)
public void calculate() {
	int i = 3 / 0;
}

假设 updateOrderStatus 方法是小赵写的方法, calculate 方法是项目中已有的功能方法,小赵只是调用了现有的 calculate 方法.
由于小赵的 updateOrderStatus 方法必须要保证,即便出现异常,也要提交事务,因此他使用了try … catch.


首先,在这种场景下,updateOrderStatus 和 calculate 方法都加了 @Transactional 注解,使用了默认的传播特性 Propagation.REQUIRED ,因此 只有一个事务. 即 updateOrderStatus 和 calculate 方法在同一个事务里.

纠正互联网上关于捕获异常事务可提交的言论_第1张图片


calculate 方法抛的异常(java.lang.ArithmeticException: / by zero)虽然被外层的 updateOrderStatus 方法捕获了,但是calculate 方法抛的异常首先是被Spring捕获, 因为calculate 方法加了@Transactional 注解,Spring是第一个感知到calculate 方法抛的异常,这个时候Spring就会把当前事务标记成回滚状态.
// org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction

protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
			final InvocationCallback invocation) throws Throwable {

	if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {

		TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

		Object retVal;
		try {
			// 调用业务方法
			retVal = invocation.proceedWithInvocation();
		}
		catch (Throwable ex) {
			// 内部会将事务标记成回滚
			completeTransactionAfterThrowing(txInfo, ex);
			// 异常继续向上抛出
			throw ex;
		}
		finally {
			cleanupTransactionInfo(txInfo);
		}
	}
}

// completeTransactionAfterThrowing 方法会调用到 doSetRollbackOnly方法

// org.springframework.jdbc.datasource.DataSourceTransactionManager#doSetRollbackOnly
protected void doSetRollbackOnly(DefaultTransactionStatus status) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject)status.getTransaction();

    txObject.setRollbackOnly();
}

public void setRollbackOnly() {
    this.getConnectionHolder().setRollbackOnly();
}

public void setRollbackOnly() {
	this.rollbackOnly = true;
}

Spring将事务标记成了 rollbackOnly = true,即当前事务只能回滚.
即便外层的 updateOrderStatus 方法捕获了异常,一旦 updateOrderStatus 方法提交事务,就会提示如下错误

在这里插入图片描述


如果在异常发生的地方(即calculate 方法内部),和捕获异常的地方(即updateOrderStatus 方法内部),这中间如果有Spring的事务代码(比如使用了@Transactional ),那么Spring是第一个感知到异常,即便业务代码中加了try … catch…也无济于事,Spring依然不会允许提交事务. 如果这中间没有Spring的事务代码,即Spring没有感知到业务代码中的异常,而且业务代码中又使用了try … catch…,那么Spring就会允许提交事务.

你可能感兴趣的:(java,spring,mybatis)