In Spring, if I have:
ServiceA.serviceA() -> ServiceB.serviceB() -> ServiceC.serviceC() ->ServiceD.serviceD()
where ServiceD.serviceD()
can throw a runtime exception: MyRuntimeException
, which is propagated back up to ServiceA.serviceA
catch block. Does it matter on which service I put @Transactional(noRollbackFor=[MyRuntimeException.class])
on?
Is there any difference between putting it on any of the services?
Note: All my Services are marked as @Transactional
As you did not give precision on that, I assume that you are using default propagation of PROPAGATION_REQUIRED
. In that context, the 4 services will use the same transaction, and if any of the three inner marks the transaction as read-only as a result of the exception, the outer will get a UnexpectedRollbackException
to inform it that the asked commit actually resulted in a rollback. From Spring Reference Manual : However, in the case where an inner transaction scope sets the rollback-only marker, the outer transaction has not decided on the rollback itself, and so the rollback (silently triggered by the inner transaction scope) is unexpected. A corresponding UnexpectedRollbackException is thrown at that point. This is expected behavior so that the caller of a transaction can never be misled to assume that a commit was performed when it really was not. So if an inner transaction (of which the outer caller is not aware) silently marks a transaction as rollback-only, the outer caller still calls commit. The outer caller needs to receive an UnexpectedRollbackException to indicate clearly that a rollback was performed instead.. And if the outer transaction decides to rollback the transaction because of the exception, the transaction will obviously be rolled back.
So if none of the services catches the exception, and if you use a propagation of PROPAGATION_REQUIRED
, at least the four involved methods have to be annotated with @Transactional(noRollbackFor=[MyRuntimeException.class])
.
An alternative of using noRollbackFor=[MyRuntimeException.class]
would be to catch the exception in the appropriate method of ServiceD
. In that case, the exception will never climb up the stack, and none of the transactional proxies will ever knows it occurred. The commit would then normally happen at the end of the transaction.
Edit per comment :
If you want further control on exception management, you could try to duplicate method : a transactional method, that calls a non transactional one in your service class. And you could call the non-transactional one if you do not want another transactional proxy in the chain. But this has sense only if this use case (a service class calling another service class with special exception requirement) is exceptional.
As an alternative, you could inject the implementations on the other service classes instead of injecting the transactional proxies (@Autowired private ServiceBImpl serviceB;
). As you are already have a transaction obtained at the outer level, all DAO operations should be fine, and as there is only one transactional proxy at the outer level, you have one single point of control for exception management. It is rather uncommon to inject classes instead of interfaces, and you should document the why in a red flashing font :-) , but it should meet your requirements.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With