在 Spring ,如果我有:ServiceA.serviceA() -> ServiceB.serviceB() -> ServiceC.serviceC() ->ServiceD.serviceD()
其中ServiceD.serviceD()
可以引发运行时异常:MyRuntimeException
,它会传播回ServiceA.serviceA
catch块。我将@Transactional(noRollbackFor=[MyRuntimeException.class])
放在哪个服务上有关系吗?
将其放在任何服务上有什么区别吗?
注意:我所有的服务都标记为@Transactional
最佳答案
由于您没有对此进行精确说明,因此我假设您使用的是PROPAGATION_REQUIRED
的默认传播。在这种情况下,这四个服务将使用相同的事务,并且如果三个内部事务中的任何一个由于异常而将事务标记为只读,则外部事务将获得一个UnexpectedRollbackException
来通知该请求的提交实际上导致了回滚。来自Spring参考手册:但是,在内部事务范围设置仅回滚标记的情况下,外部事务尚未决定回滚本身,因此回滚(由内部事务范围默默触发)是意外的。此时将引发相应的UnexpectedRollbackException。这是预期的行为,因此决不能误导事务的调用方以假定在确实未执行提交的情况下执行该提交。因此,如果内部事务(外部调用者不知道)将一个事务静默标记为仅回滚,则外部调用者仍会调用commit。外部调用方需要接收一个UnexpectedRollbackException,以清楚地指示已执行回滚。如果外部事务由于异常而决定回滚该事务,则显然该事务将被回滚。
因此,如果所有服务均未捕获到异常,并且您使用PROPAGATION_REQUIRED
的传播,则至少必须使用@Transactional(noRollbackFor=[MyRuntimeException.class])
注释涉及的四个方法。
使用noRollbackFor=[MyRuntimeException.class]
的替代方法是在ServiceD
的适当方法中捕获异常。在这种情况下,异常将永远不会爬上堆栈,并且任何事务代理都不会知道它的发生。提交通常会在事务结束时进行。
按评论编辑:
如果要进一步控制异常管理,则可以尝试复制method:一种事务方法,该方法在您的服务类中调用非事务方法。如果您不想在链中使用另一个事务代理,则可以调用非事务代理。但这只有在这种用例(具有特殊异常要求的调用另一个服务类的服务类)例外的情况下才有意义。
作为替代方案,您可以在其他服务类上注入实现,而不是注入事务代理(@Autowired private ServiceBImpl serviceB;
)。因为您已经在外部级别获得了事务,所以所有DAO操作都应该很好,并且由于在外部级别只有一个事务代理,因此您可以对异常管理进行单一控制。注入类而不是接口是很不常见的,您应该以闪烁的红色字体(-)记录出为什么,但是它应该满足您的要求。