Spring 3/Hibernate的事务回滚最佳实践

19

参考Spring文档

任何RuntimeException都会触发回滚,而任何已检查的异常将不会。

参考javapractices.com

Unchecked exceptions:

  • 表示程序中的缺陷(错误) - 经常是传递给非私有方法的无效参数。引用《Java编程语言》(Gosling、Arnold和Holmes):“未经检查的运行时异常表示条件,一般来说,反映了您的程序逻辑中的错误,并且无法在运行时合理地恢复。”
  • 是RuntimeException的子类,并且通常使用IllegalArgumentException、NullPointerException或IllegalStateException实现
  • 方法不需要为其实现引发的未经检查的异常建立策略(它们几乎从不这样做)

Checked exceptions:

  • 表示程序控制范围之外的无效条件(无效用户输入、数据库问题、网络中断、缺少文件)
  • 是Exception的子类
  • 方法需要为其实现引发的所有已检查异常建立策略(将已检查异常传递到堆栈上方,或以某种方式处理)

如果在我的业务逻辑中发现问题并且我想回滚更改,那么我必须抛出新的RuntimeException吗?这实际上不是RuntimeException(未经检查的异常),因为我已经在逻辑中识别了它。或者可能我误解了这些概念?

我的真正问题是,在我的@Transactional服务方法中,回滚事务的最佳实践是什么?

5个回答

13

如果你使用了检查型异常,只需将它们添加到@Transactional注解的rollbackFor属性中即可。

@Transactional(rollbackFor = { MyInvalidUserException.class, MyApplicationException.class })
public void method() throws MyInvalidUserException,  MyApplicationException { 
    ... 
    ... 
}

org.life.java 的回答也可以正常工作。如果你想将编程式事务管理与声明式事务混合在一起,或者保持严格的声明式事务,则这是一个学术上的决定。


1
如果我在我的方法中抛出异常,该如何返回某些东西?另外,抛出异常相当昂贵。难道没有任何方法可以手动回滚事务而不触发异常吗? - Guillaume Polet
@GuillaumePolet 有人可能会反驳说,如果你故意将回滚作为非异常业务流程的一部分进行,那么在RDBMS中进行回滚所涉及的成本将比现代JVM上的任何真实或感知成本都要高出数个数量级,并且整个设计应该重新审视。无论如何,如果您不喜欢声明式事务的工作方式...请不要使用它们。该功能绝不是您唯一的选择。您仍然可以使用普通的自定义Hibernate会话事务。 - Affe

7

要么在程序中从内部进行回滚:

@Transactional
public void commit() {
  try {
    // some business logic...
  } catch (ConstraintViolationException e) {
    // trigger rollback programmatically
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
  }
}

或者将异常标记为回滚并从调用方处理它:
@Transactional(rollBackFor = TransactionException.class)
public void commit() throws ConstraintViolationException{
    try {
    // some business logic...
  } catch (ConstraintViolationException e) {
    // handle the exception 
    // re-throw for rollback
    new TransactionException(e);
  }
}

public void doCommit(){
  try {
    commit()
  } catch (TransactionException e){
    // do nothing as already handled
  }
}

我更倾向于前者,因为它使代码更简单,但是根据Spring文档,这种做法是不鼓励的

如果您绝对需要,可以使用编程方式进行回滚,但是它的使用与实现基于POJO的整洁架构相矛盾。


4
应该像这样:
@Transactional
public void method () throws YourCustomException  {
   try{
           //logic  
   }catch(Exception ex){
             TransactionAspectSupport.currentTransactionStatus()
                        .setRollbackOnly();
             throw(new YourCustomException(ex.getMessage()));
   }
}

5
...那为什么应该是这样的呢? - meriton
@meriton,这将适用于所有情况,我的意思是您想要显示此服务正在抛出此异常的目的将得到解决,同时您的事务处理也将得到处理。 - jmj

2
我认为可以说对于应该检查哪些异常存在不同的意见。例如,考虑来自Spring Framework介绍的以下摘录:
Spring数据访问异常层次结构基于未经检查的(运行时)异常。通过在几个项目中使用Spring,我越来越确信这是正确的决定。
数据访问异常通常是不可恢复的。例如,如果我们无法连接到数据库,则特定业务对象不太可能能够解决问题。一个潜在的例外是乐观锁定违规,但并非所有应用程序都使用乐观锁定。强制编写捕获不能明智处理的致命异常的代码通常是不好的。让它们传播到顶级处理程序(如servlet或EJB容器)通常更合适。所有Spring数据访问异常都是DataAccessException的子类,因此,如果我们选择捕获所有Spring数据访问异常,我们可以轻松地做到这一点。
请注意,如果我们确实想从未经检查的数据访问异常中恢复,我们仍然可以这样做。我们可以编写代码仅处理可恢复条件。例如,如果我们认为只有乐观锁定违规是可恢复的,我们可以在Spring DAO中编写以下代码:
try { // do work } catch (OptimisticLockingFailureException ex) { // I'm interested in this }
如果Spring数据访问异常是已检查的,则需要编写以下代码。请注意,我们仍然可以选择编写此代码:
try { // do work } catch (OptimisticLockingFailureException ex) { // I'm interested in this } catch (DataAccessException ex) { // Fatal; just rethrow it }
对于第一个示例的一个潜在反对意见 - 编译器无法强制执行处理可能可恢复的异常 - 也适用于第二个示例。由于我们被迫捕获基本异常(DataAccessException),编译器不会强制执行子类(OptimisticLockingFailureException)的检查。因此,编译器将强制我们编写处理不可恢复问题的代码,但不提供帮助来强制我们处理可恢复的问题。
Spring使用未经检查的数据访问异常与许多成功的持久性框架一致。 (实际上,它部分受JDO启发。)JDBC是少数几个使用已检查异常的数据访问API之一。例如,TopLink和JDO仅使用未经检查的异常。Hibernate在3版中从已检查异常切换到未经检查的异常。

数据访问异常显然是程序的直接控制范围之外,因此根据javapractices的说法,它们应该被检查。但是spring源码中的人持不同意见。我更相信他们的判断。

因此,我认为抛出未经检查的异常来指示事务应该回滚没有任何问题。当然,您也可以使用已检查的异常,并配置方面也回滚它们。 (有关详细信息,请参见Affe的答案)


2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接