• 第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);  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    只要为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
        // ...
      }
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    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);      
        }
       }
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

    txTemplate.execute(new TransactionCallbackWithoutResult() {
    	@Override
    	protected void doInTransactionWithoutResult(TransactionStatus txStatus) {
    		boolean needRollback = false;
        ...
          
    		if(needRollback)
    			txStatus.setRollbackOnly();
    }});
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    对于事务操作中可能抛出的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();
      	}
    	}
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这种情况下,需要注意一个问题,千万不要只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();
        }
      }
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    当然,如果在转账期间的异常是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传播行为的声明式事务管理方式。

  • 相关阅读:
    jQuery获取更改标签内容、操作标签属性:html()、text()、val()、attr()、prop()
    Android自动化测试中使用ADB进行网络状态管理!
    shell入门概述
    SqlServer中的集合运算符
    Linux多线程
    如何与ChatGPT愉快地聊天
    对接淘宝天猫平台的第一篇
    HarmonyOS 习题(二)
    HTML静态网页成品作业(HTML+CSS)——贵州美食介绍设计制作(1个页面)
    论文阅读 Streaming Graph Neural Networks
  • 原文地址:https://blog.csdn.net/qq_34626094/article/details/125443568