Java 嵌套@Transactional方法中的Spring意外回滚异常
我有一个dao类(Java 嵌套@Transactional方法中的Spring意外回滚异常,java,spring,hibernate,transactions,Java,Spring,Hibernate,Transactions,我有一个dao类(MyDao),它用@Transactional注释(在类级别上)标记,没有附加参数。在这个dao类中,我有一个方法,在某些情况下需要抛出一个已检查的异常并执行事务回滚。大概是这样的: @Transactional public class MyDao { public void daoMethod() throws MyCheckedException() { if (somethingWrong) { Transaction
MyDao
),它用@Transactional
注释(在类级别上)标记,没有附加参数。在这个dao类中,我有一个方法,在某些情况下需要抛出一个已检查的异常并执行事务回滚。大概是这样的:
@Transactional
public class MyDao {
public void daoMethod() throws MyCheckedException() {
if (somethingWrong) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
throw new MyCheckedException("something wrong");
}
}
这个很好用。但是,此dao方法是从服务方法调用的,服务方法也标记为@Transactional:
public class MyService {
@Autowired
private MyDao myDao;
@Transactional
public void serviceMethod() throws MyCheckedException {
myDao.daoMethod();
}
}
问题是,当从serviceMethod()
调用daoMethod()
并将事务标记为仅回滚时,我会得到一个意外回滚异常
在引擎盖下,Spring创建了两个事务拦截器:一个用于MyDao
,另一个用于MyService
。当daoMethod()
将事务标记为回滚时,MyDao的拦截器执行回滚并返回。但随后堆栈移动到MyService
的拦截器,该拦截器会发现上一次回滚,并抛出UnexpectedRollbackException
一种解决方案是从MyDao中删除@Transactional
注释。但是现在这是不可行的,因为很多地方都使用了MyDao,这可能会导致错误
另一种解决方案是,不要仅在daoMethod()
中将事务设置为回滚,而是标记serviceMethod()
以在MyCheckedException
上还原事务。但我不喜欢这个解决方案,因为我有很多这样的“服务方法”,我必须显式地标记所有这些方法才能回滚该异常。此外,将来添加新服务方法的每个人都必须考虑到这一点,因此它会为错误创造机会
有没有一种方法可以让我只从daommethod()
将事务设置为回滚,并防止Spring抛出意外回滚异常
?例如,由于一些参数的组合隔离
和传播
?在事务内部引发异常已经触发回滚,因此使用setRollbackOnly()在这里是多余的,这可能就是您出现该错误的原因
如果DAO上的Transactionnal是使用默认的Propagation.REQUIRED设置的,那么如果已有事务,它将重用现有事务;如果没有事务,它将创建现有事务。在这里,它应该重用在服务层创建的事务。我找到了答案。我还必须明确地告诉“外部”拦截器,我要回滚事务。换句话说,两个拦截器都需要“通知”回滚。这意味着在serviceMethod()
中捕获MyCheckedException
,并将事务状态设置为仅回滚,或者像这样标记serviceMethod()
@Transactional(rollboor=MyCheckedException.class)
但正如我在OP中提到的,我想避免这种情况,因为它容易出错。另一种方法是在默认情况下使@Transactional
回滚到MyCheckedException
。但默认情况下,这是一个仅限Spring在运行时异常时回滚的事务。这可以通过@Transactional注释上的属性rollboor
进行修改。但是当我在MyDao上执行此操作时,得到的结果与设置事务状态相同。您应该使用框架配置回滚检查的异常,而不是手动更改事务状态,您可以将rollbackor on exception.class设置为回滚所有检查的异常。您可以将其放在所有服务上(或者使用该配置创建一个可替代的注释)。您还可以将选中的异常放在运行时异常中,并将其抛出。我不知道您的用例,但它可能是一个解决方案。我的问题不是“如何回滚事务”。我的问题是,当我有两个“嵌套”拦截器时,内部拦截器执行回滚,然后外部拦截器抛出UnexpectedRollbackException。我告诉Spring执行回滚的方式并不重要。