英文:
How to handle Exceptions in Repository?
问题
使用Spring 5.3,我有一个存储库:
@Transactional(TransactionMode.REQUIRES_NEW)
public class Repository {
public List<Object> hasData() {
...
return entityManager.createQuery(query).getResultList();
}
}
这个方法被频繁调用,数据库连接必须被视为不可靠。因此,代码必须考虑查询失败的可能性,因为数据库连接已关闭。在这种情况下的期望行为是返回一个空的Collection
。
我已经在存储库内部封装了调用,如下所示:
@Transactional(TransactionMode.REQUIRES_NEW)
public class Repository {
public List<Object> hasData2() {
try {
return hasData();
} catch (RuntimeException e) {
// 进行一些日志记录
}
}
public List<Object> hasData() {
...
return entityManager.createQuery(query).getResultList();
}
}
然而,当Hibernate调用失败时,Hibernate会将连接标记为只读。因此,Spring代码关闭事务时会出现UnexpectedRollbackException
异常。
我的下一步是要么将这个Repository
包装到第二个存储库中,只关心捕获UnexpectedRollbackException
,要么包装这个存储库的每个调用。
这两种解决方案都似乎有些不对劲。
有没有办法阻止Spring抛出UnexpectedRollbackException
,或者阻止Hibernate将事务设置为仅回滚?在数据库失败不是异常而是被接受状态的情况下,有什么模式(从调用者的角度)?
英文:
Using Spring 5.3 I have a repository
@Transactional(TransactionMode.REQUIRES_NEW)
public class Repository {
public List<Object> hasData() {
...
return entityManager.createQuery(query).getResultList();
}
}
This method is called quite frequently and the database connection has to be assumed as non-reliable. So the code has to consider the possibility, the query fails because the database connection is closed. The desired behavior in this case is to return an empty Collection
.
I have wrapped the call within the repo like
@Transactional(TransactionMode.REQUIRES_NEW)
public class Repository {
public List<Object> hasData2() {
try {
return hasData();
} catch (RuntimeException e) {
// do some logging
}
}
public List<Object> hasData() {
...
return entityManager.createQuery(query).getResultList();
}
}
However, when the hibernate call fails, hibernate marks the connection as read-only. Consequently, the spring code closing the transaction will fail with an UnexpectedRollbackException
.
My next step is either wrapping this Repository
into a second one, only concerned catching the UnexpectedRollbackException
or by wrapping every call of this repository.
Both solutions seem wrong somehow.
Is there any way to prevent Spring from throwing the UnexpectedRollbackException
or hibernate from setting the Transaction to rollback-only? What is the pattern when a failure of the database is not an exception but an accepted state (form the POV of the caller)?
答案1
得分: 1
一般来说,在事务方法执行期间发生运行时异常时,将事务标记为仅回滚是预期的行为。事务注解的目的是提供一组数据库操作的一致和原子执行,如果发生错误,通常比继续使用可能不一致的数据更安全地回滚事务并重新开始。
然而,如果你想避免抛出 UnexpectedRollbackException,一个可能的解决方案是在调用代码中捕获异常并在那里处理。例如:
public List<Object> someMethod() {
try {
return repository.hasData2();
} catch (UnexpectedRollbackException e) {
// 处理异常并返回空集合
return Collections.emptyList();
}
}
另一个选择是配置Spring将UnexpectedRollbackException视为非致命异常,这将阻止它传播到调用代码。可以通过将以下内容添加到Spring配置中实现:
<bean id="transactionAttributeSource" class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"/>
<bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributeSource" ref="transactionAttributeSource"/>
<property name="rollbackOn" value="RuntimeException"/>
<property name="nonTransactionalMethod" value="readOnly"/>
</bean>
<aop:config>
<aop:advisor advice-ref="transactionInterceptor" pointcut="execution(* com.example.*.*(..))"/>
</aop:config>
这个配置将只在显式抛出 RuntimeException(比如通过调用 throw new RuntimeException())时回滚事务,而不是对包括 UnexpectedRollbackException 在内的所有运行时异常都进行回滚。
英文:
In general, marking the transaction as rollback-only is the expected behavior in case of a runtime exception occurring during a transactional method. The purpose of the transactional annotation is to provide a consistent and atomic execution of a set of database operations, and if an error occurs, it's usually safer to roll back the transaction and start over rather than continuing with potentially inconsistent data.
However, if you want to avoid the UnexpectedRollbackException being thrown, one possible solution would be to catch the exception in your calling code and handle it there.
For example:
public List<Object> someMethod() {
try {
return repository.hasData2();
} catch (UnexpectedRollbackException e) {
// handle the exception and return an empty collection
return Collections.emptyList();
}
}
Another option would be to configure Spring to treat the UnexpectedRollbackException as a non-fatal exception, which would prevent it from being propagated to the calling code. You can do this by adding the following to your Spring configuration:
<bean id="transactionAttributeSource" class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"/>
<bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributeSource" ref="transactionAttributeSource"/>
<property name="rollbackOn" value="RuntimeException"/>
<property name="nonTransactionalMethod" value="readOnly"/>
</bean>
<aop:config>
<aop:advisor advice-ref="transactionInterceptor" pointcut="execution(* com.example.*.*(..))"/>
</aop:config>
This configuration will cause the transaction to be rolled back only if a RuntimeException is thrown explicitly (such as by calling throw new RuntimeException()), rather than for all runtime exceptions including the UnexpectedRollbackException.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论