第20章 使用Spring进行事务管理(一)

第20章 使用Spring进行事务管理

本章内容:

  • 编程式事务管理
  • 声明式事务管理

事务管理的实施通常有两种方式,即编程式事务管理和声明式事务管理。对于这两种事务管理方式的支持,Spring事务框架可以说是青出于蓝而胜于蓝。

20.1 编程式事务管理

通过Spring进行编程式事务管理有两种方式,要么直接使用PlatformTransactionManager,要么使用更方便的TransactionTemplate。二者各有优缺点,但总体上来说,推荐使用TransactionTemplate进行编程式事务管理。

20.1.1 直接使用PlatformTransactionManager进行编程式事务管理

PlatformTransactionManager接口定义了事务界定的基本操作,我们可以直接使用PlatformTransactionManager进行编程式事务管理,如下方代码清单所示。

DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
definition.setTimeout(20);
...
TransactionStatus txStatus = transactionManager.getTransaction(definition);
try {
    
    
	// 业务逻辑实现
}
catch(App1icationException e) {
    
    
	transactionManager.rollback(txStatus);
	throw e;
)
catch(RuntimeException e) {
    
    
	transactionManager.rollback(txStatus);
	throw e;
}
catch(Error e) {
    
    
	transactionManager.rollback(txStatus);
	throw e;
}
transactionManager.commit(txStatus);  

只要为transactionManager提供合适的platformTransactionManager实现,然后结合TransactionDefinition开启事务,并结合TransactionStatus来回滚或者提交事务,就可以完成针对当前对象的整个事务管理。

直接使用PlatformTransactionManager,我们可以完全控制整个事务处理过程,但是,缺点也是很明显的。从抽象事务操作以屏蔽不同事务管理API差异的角度看,PlatformTransactionManager可能已经足够了。但是,从应用程序开发的角度来看,却依然过于底层,只是期间的这些异常处理就够我们忙活的了。如果在每个需要事务管理的地方,全都使用PlatformTransactionManager进行事务管理,那么重复代码的数量将是惊人的。

鉴于使用PlatformTransactionManager进行事务管理的流程比较固定,各个事务管理期间只有部分逻辑存在差异,我们可以考虑像Spring的数据访问层那样,**使用模板方法模式与Callback相结合的方式,对直接使用PlatformTransactionManager进行事务管理的代码进行封装。**这就有了更方便的编程式事务管理方式,即使用TransactionTemplate的编程式事务管理。

20.1.2 使用TransactionTemplate进行编程式事务管理

org.springframework.transaction.support.TransactionTemplate对与PlatformTransactionManager相关的事务界定操作以及相关的异常处理进行了模板化封装,开发人员更多地关注于通过相应的Callback接口提供具体的事务界定内容即可。

Spring针对TransactionTemplate提供了两个Callback接口,TransactionCallbackTransactionCallbackWithoutResult,二者的唯一区别就是是否需要返回执行结果。

使用TransactionTemplate进行事务管理的代码,看起来要比直接使用PlatformTransactionManager要简洁并且容易管理得多,如下方代码清单所示。

TransactionTemplate txTemplate = ...;
Object result = txTemplate.execute(new TransactionCallback() {
    
    
	public Object doInTransaction(TransactionStatus txStatus) {
    
    
		Object result = null;
		// 各种事务操作.....
		return result;
	}
});
或者
txTemplate.execute(new TransactionCallbackWithoutResult() {
    
    
  @Override
  protected void doInTransactionWithoutResult(TransactionStatus txStatus) {
    
    
    // 事务操作1
    // 事务操作2
    // ...
  }
});

TransactionTemplate会捕捉TransactionCallback或者TransactionCallbackWithoutResult事务操作中抛出的unchecked exception并回滚事务,然后将unchecked exception抛给上层处理。所以,现在我们只需要处理特定于应用程序的异常即可,而不用像直接使用PlatformTransactionManager那样,对所有可能的异常都进行处理。

如果事务处理期间没有任何问题,TransactionTemplate最终会为我们提交事务,唯一需要我们干预的就只剩下某些情况下的事务回滚了。如果在TransactionCallback或者TransactionCallbackWithoutResult的事务操作过程中需要让当前事务回滚而不是最终提交,一般来说,我们有如下两种方式。

  • 抛出unchecked exception,TransactionTemplate会为我们处理事务的回滚。如果事务操作中可能抛出checked exception,那么就得在Callback内部捕获,然后转译为unchecked exception后抛出。 下方代码清单给出了针对这种情况的处理代码示例。
txTemplate.execute(new TransactionCallbackWithoutResult() {
    
    
	@Override
	protected void doInTransactionWithoutResult(TransactionStatus txStatus) {
    
    
		try {
    
    
      ...
    }
		catch(CheckedException e) {
    
    
			// 抛出特定的异常类型以避免一般情况下使用的RuntimeException
			throw new RuntimeException(e);      
    }
   }
});

使用Callback接口公开的TransactionStatus将事务标记为rollBackonly。**TransactionTemplate在最终提交事务的时候,如果检测到rollBackOnly标志状态被设置,将把提交事务改为回滚事务。**下方代码清单演示了这种情况。

txTemplate.execute(new TransactionCallbackWithoutResult() {
    
    
	@Override
	protected void doInTransactionWithoutResult(TransactionStatus txStatus) {
    
    
		boolean needRollback = false;
    ...
      
		if(needRollback)
			txStatus.setRollbackOnly();
}});

对于事务操作中可能抛出的checked exception,如果既想回滚事务,又不想让它以unchecked exception的形式向上层传播的话,我们当然不能通过将其转译成unchecked exception的方式来处理。我们现在却可以通过TransactionStatus设置rollBackOnly来达到以上“一箭双雕”的目的(见下方代码清单)。

txTemplate.execute(new TransactionCallbackWithoutResult() {
    
    
	@Override
	protected void doInTransactionWithoutResult(TransactionStatus txStatus) {
    
    
		try {
    
    
    	...
	  }
		catch(CheckedException e) {
    
    
			logger.warn("Transaction is Rolled back!", e);
			txStatus.setRollbackOnly();
  	}
	}
});

这种情况下,需要注意一个问题,千万不要只txStatus.setRollbackOnly()而忘记记录日志。虽然你可能知道“swallow exception”是不对的,但这个地方确实容易忽略日志的记录,从而造成事务回滚了,而我们却不知道的情况。当发现数据库中本来应该删除的数据却依然存在,并且日志中也没有任何异常信息的时候,你就得花很长时间来想到底哪里出了问题了。

虽然使用TransactionTemplate要比直接使用PlatformTransactionManager更加便捷,但TransactionTemplate无法处理当事务操作中需要向上层抛出原来的checked exception的情况。 你应该也发现了,实际上TransactionCallback或者TransactionCallbackwithoutResult的方法定义中没有声明抛出任何checked exception,直接使用PlatformTransactionManager则没有这样的限制。不过,这应该并不会过多地限制TransactionTemplate展现其“个人魅力”吧?

20.1.3 编程创建基于Savepoint的嵌套事务

TransactionStatus不但可以在事务处理期间通过setRollbackOnly()方法来干预事务的状态,如果需要,作为SavepointManager,它也可以帮助我们使用Savepoint机制来创建嵌套事务。

以银行账户间转账为例,来说明如何使用TransactionStatus创建基于Savepoint的嵌套事务。现在不是从一个账户转到另一个账户,而是从一个账户转到两个账户,一个是主账户,一个备用账户。如果向主账户转账失败,则将金额转入备用账户。总之,金额从第一个账户取出之后,必须存入两个账户的其中一个,以保证整个事务的完整性。在这样的前提下,我们的事务管理代码基本上如下方代码清单所示。

txTemplate.execute(new TransactionCallbackWithoutResult() {
    
    
	@Override
	protected void doInTransactionWithoutResult(TransactionStatus txStatus) {
    
    
		BigDecimal transferAmount = new BigDecimal("20000");
		try {
    
    
			withdraw("WITHDRAW_ACOUNT_ID", transferAmount);
			Object savePointBeforeDeposit = txStatus.createSavepoint();
			try {
    
    
        deposit("MAIN_ACOUNT_ID", transferAmount);
      }
			catch(DepositException ex) {
    
    
				logger.warn("rollback to savepoint for main acount transfer failure", ex);
				txStatus.rollbackToSavepoint(savePointBeforeDeposit);
        
				deposit("SECONDARY_ACOUNT_ID", transferAmount);
      }
			finally {
    
    
				txStatus.releaseSavepoint(savePointBeforeDeposit);
      }
    }
    catch(TransferException e) {
    
    
			logger.warn("failed to complete transfer operation!", e);
			txStatus.setRo1lbackOnly();
    }
  }
});

当然,如果在转账期间的异常是unchecked exception,最外层的捕捉TransferException是没有太多必要的(这种情况下,TransactionTemplate将自动回滚事务)。

在这里,使用Savepoint创建嵌套事务的好处是,即使deposit过程中涉及多笔数据的更新,通过txStatus.rollbackToSavepoint(savePointBeforeDeposit)也可以将这些数据恢复到没有存入金额之前的状态,而不会破坏当前事务的完整性。

如果在这里通过传播行为是PROPAGATION_REQUIRES_NEW的TransactionDefinition创建一个新的事务的话,虽然deposit过程中出现问题也可以回滚数据,但取款与存款的操作就不在同一个事务中了(取款在当前事务,存款在另一个新的事务),这无疑违反了事务的ACID属性。

注意:使用TransactionStatus创建基于Savepoint的嵌套事务需要底层的PlatformTransactionManager实现类的支持,当前只有在JDBC3.0驱动下的DataSourceTransactionManager可用。

通过使用TransactionStatus创建基于Savepoint的嵌套事务并非创建嵌套事务的唯一方式,也并非最方便的方式。实际上,我们更倾向于使用结合PROPAGATION_NESTED传播行为的声明式事务管理方式。

猜你喜欢

转载自blog.csdn.net/qq_34626094/article/details/125443568