为什么事务回滚在RuntimeException而不是SQLException

时间:2011-08-19 18:30:42

标签: spring transactions

我有一个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,但这看起来很奇怪。

3 个答案:

答案 0 :(得分:40)

这是定义的行为。来自docs

  

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

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

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

如果您希望这样做,可以更改此行为,但如何执行此操作取决于您使用Spring API的方式以及如何设置事务管理器。

答案 1 :(得分:2)

Spring广泛使用RuntimeExceptions(包括使用DataAccessExceptions来包装来自ORM的SQLExceptions或异常),以防止从异常中恢复。它假定您要使用已检查的异常,以便在需要通知某个服务上层的某些情况时,但您不希望该事务受到干扰。

如果你正在使用Spring,你也可以使用它的jdbc-wrapping库和DataAccessException-translating工具,它将减少你必须维护的代码量并提供更有意义的异常。还有一个服务层抛出特定于实现的异常是一个难闻的气味。 Spring之前的方法是创建与实现无关的检查异常,包含特定于实现的异常,这导致了大量忙碌工作和繁琐的代码库。 Spring的方式避免了这些问题。

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

答案 2 :(得分:1)

对于@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());
}