Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does it matter where I place noRollbackFor?

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

like image 931
More Than Five Avatar asked Aug 25 '14 15:08

More Than Five


1 Answers

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.

like image 83
Serge Ballesta Avatar answered Oct 02 '22 19:10

Serge Ballesta