我有一个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,但这看起来很奇怪。
答案 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
不会触发事务回滚;可以使用rollbackFor
和noRollbackFor
注释参数来配置行为。
@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());
}