【问题标题】:why does transaction roll back on RuntimeException but not SQLException为什么事务会在 RuntimeException 而不是 SQLException 上回滚
【发布时间】:2011-10-30 20:22:57
【问题描述】:

我有一个 Spring 管理的服务方法来管理数据库插入。它包含多个插入语句。

@Transactional
public void insertObservation(ObservationWithData ob) throws SQLException 
{
    observationDao.insertObservation(ob.getObservation());
            // aop pointcut inserted here in unit test
    dataDao.insertData(ob.getData());
}

我有两个单元测试在调用第二个插入之前抛出异常。如果异常是 RuntimeException,则回滚事务。如果异常是 SQLException,则保留第一个插入。

我很困惑。谁能告诉我为什么事务不会在 SQLException 上回滚?任何人都可以提供如何管理这个的建议吗?我可以捕获 SQLException 并抛出 RuntimeException,但这看起来很奇怪。

【问题讨论】:

  • 根据 skaffman 的回答和 Nathan 的提示,我在 DAO 层捕获了所有 SQLExceptions,并使用 Spring 的 SQLExceptionTranslator 将它们转换为未经检查的 DataAccessExceptions。这个 Spring 论坛帖子 forum.springsource.org/… 和 Robert Martin 在“清洁代码。敏捷软件工艺手册”(Prentice Hall,2008 年)中的错误处理章节也有助于阐明为什么抛出未经检查的异常可以保持更好的模块化。跨度>

标签: spring transactions


【解决方案1】:

这是定义的行为。来自docs

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

这是所有 Spring 事务 API 的常见行为。默认情况下,如果从事务代码中抛出RuntimeException,则事务将回滚。如果抛出已检查异常(即不是RuntimeException),则不会回滚事务。

这背后的基本原理是 RuntimeException 类通常被 Spring 用来表示不可恢复的错误情况。

如果您愿意,可以从默认值更改此行为,但如何更改取决于您如何使用 Spring API 以及如何设置事务管理器。

【讨论】:

  • 是的,这是正确的,尽管我并不完全理解这一点。我想抛出检查的 SQLException,以便应用程序代码可以处理它(记录、继续、停止执行等),但我不希望事务成功。我可以将每个事务注释设置为针对异常回滚,但我更愿意设置事务管理器以在应用程序范围内执行该操作。我可以配置基本的 DataSourceTransactionManager 来做到这一点并不是很明显。如果您有对此进行更丰富讨论的建议,我将不胜感激。我现在正在参加春季论坛。谢谢!
【解决方案2】:

对于@Transactional,默认情况下,回滚仅针对运行时发生,仅限未经检查的异常。因此,您检查的异常SQLException 不会触发事务的回滚;可以使用rollbackFornoRollbackFor 注释参数配置行为。

@Transactional(rollbackFor = SQLException.class)
public void insertObservation(ObservationWithData ob) throws SQLException 
{
    observationDao.insertObservation(ob.getObservation());
            // aop pointcut inserted here in unit test
    dataDao.insertData(ob.getData());
}

【讨论】:

  • 我认为这将使事务仅回滚 SQL Exception 及其子类,而不会回滚其他类型。
【解决方案3】:

对于无法从异常中恢复的情况,Spring 广泛使用了 RuntimeExceptions(包括使用 DataAccessExceptions 来包装 SQLExceptions 或 ORM 中的异常)。它假定您希望在需要通知服务之上的层的情况下使用检查异常,但您不希望事务受到干扰。

如果您使用 Spring,您不妨利用它的 jdbc 包装库和 DataAccessException 翻译工具,它将减少您必须维护的代码量并提供更有意义的异常。还有一个服务层抛出特定于实现的异常是一种难闻的气味。春季之前的方法是创建与实现无关的检查异常,包装特定于实现的异常,这导致了很多繁忙的工作和臃肿的代码库。 Spring 的方式避免了这些问题。

如果你想知道为什么 Spring 选择让事情以这种方式工作,那可能是因为他们使用 AOP 来添加事务处理。他们不能改变他们包装的方法的签名,所以检查异常不是一个选项。

【讨论】:

    【解决方案4】:

    如果有spring事务注解的方法抛出未经检查的异常(任何异常扩展RunTimeException),事务将被回滚。如果抛出检查异常(IOException、SQLException 等),事务将不会回滚。 您可以使用来自 lombok 的https://projectlombok.org/features/SneakyThrows,如果您希望事务回滚任何类型的异常。

    【讨论】:

      猜你喜欢
      • 2018-12-27
      • 2017-07-22
      • 1970-01-01
      • 1970-01-01
      • 2023-03-05
      • 2021-04-06
      • 1970-01-01
      • 2013-03-23
      • 2013-07-02
      相关资源
      最近更新 更多