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