为什么事务会在 RuntimeException 上回滚而不是 SQLException
我有一个 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,但这看起来很奇怪。
I have a Spring-managed service method to manage database inserts. It contains multiple insert statements.
@Transactional
public void insertObservation(ObservationWithData ob) throws SQLException
{
observationDao.insertObservation(ob.getObservation());
// aop pointcut inserted here in unit test
dataDao.insertData(ob.getData());
}
I have two unit tests which throw an exception before calling the second insert. If the exception is a RuntimeException, the transaction is rolled back. If the exception is a SQLException, the first insert is persisted.
I'm baffled. Can anyone tell me why the transaction does not roll back on a SQLException? Can anyone offer a suggestion how to manage this? I could catch the SQLException and throw a RuntimeException, but that just seems weird.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
这是定义的行为。来自 文档:
这是所有 Spring 事务 API 的常见行为。默认情况下,如果事务代码中抛出 RuntimeException,则事务将回滚。如果抛出检查异常(即不是
RuntimeException
),则事务将不会回滚。这背后的基本原理是 Spring 通常采用 RuntimeException 类来表示不可恢复的错误情况。
如果您愿意,可以更改默认行为,但如何更改取决于您如何使用 Spring API 以及如何设置事务管理器。
This is defined behaviour. From the docs:
This is common behaviour across all Spring transaction APIs. By default, if a
RuntimeException
is thrown from within the transactional code, the transaction will be rolled back. If a checked exception (i.e. not aRuntimeException
) is thrown, then the transaction will not be rolled back.The rationale behind this is that
RuntimeException
classes are generally taken by Spring to denote unrecoverable error conditions.This behaviour can be changed from the default, if you wish to do so, but how to do this depends on how you use the Spring API, and how you set up your transaction manager.
对于@Transactional,默认情况下,回滚仅在运行时发生,仅发生未经检查的异常。因此,您的检查异常
SQLException
不会触发事务回滚;可以使用rollbackFor
和noRollbackFor
注释参数配置该行为。For
@Transactional
, by default, rollback happens for runtime, unchecked exceptions only. Thus, your checked exceptionSQLException
does not trigger a rollback of the transaction; the behavior can be configured with therollbackFor
andnoRollbackFor
annotation parameters.对于无法从异常中恢复的情况,Spring 广泛使用 RuntimeException(包括使用 DataAccessException 来包装 SQLException 或来自 ORM 的异常)。它假设您希望在服务之上的层需要收到某些通知的情况下使用已检查的异常,但您不希望事务受到干扰。
如果您使用 Spring,您不妨使用它的 jdbc 包装库和 DataAccessException 转换工具,它将减少您必须维护的代码量并提供更有意义的异常。另外,服务层抛出特定于实现的异常也是一种不好的感觉。 Spring 之前的方法是创建与实现无关的检查异常来包装特定于实现的异常,这导致了大量的繁忙工作和臃肿的代码库。 Spring的方式避免了这些问题。
如果您想知道为什么 Spring 选择以这种方式工作,可能是因为他们使用 AOP 来添加事务处理。他们无法更改所包装方法的签名,因此检查异常不是一个选项。
Spring makes extensive use of RuntimeExceptions (including using DataAccessExceptions to wrap SQLExceptions or exceptions from ORMs) for cases where there's no recovering from the exception. It assumes you want to use checked exceptions for cases where a layer above the service needs to be notified of something, but you don't want the transaction to be interfered with.
If you're using Spring you might as well make use of its jdbc-wrapping libraries and DataAccessException-translating facility, it will reduce the amount of code you have to maintain and provide more meaningful exceptions. Also having a service layer throwing implementation-specific exceptions is a bad smell. The pre-Spring way was to create implementation-agnostic checked exceptions wrapping implementation-specific exceptions, this resulted in a lot of busy-work and a bloated code base. Spring's way avoids those problems.
If you want to know why Spring chose to make things work this way, it's probably because they use AOP to add the transaction-handling. They can't change the signature of the method they are wrapping, so checked exceptions aren't an option.
如果从带有 Spring 事务注释的方法抛出未经检查的异常(任何扩展 RunTimeException 的异常),则事务将被回滚。如果抛出检查异常(IOException、SQLException 等),事务将不会回滚。
您可以使用 lombok 的 https://projectlombok.org/features/SneakyThrows,如果您想要事务回滚任何类型的异常。
If an unchecked exception (any exception extends RunTimeException) is thrown from the method that has spring transactional annotation, the transaction will be rolled back. If a checked exception (IOException, SQLException etc.) is thrown, the transaction will not be rolled back.
You can use https://projectlombok.org/features/SneakyThrows from lombok, If you want to the transaction rolled back any kind of exception.