I am trying to figure out how to best handle persistence (and potentially other) exceptions in combination with Spring's @Transactional
. For this post I am just going to take the simple example of a user registration, which can cause DataIntegrityViolationException
due to duplicate username.
The following things I have tried and they are not really satisfactory to me:
val entity = UserEntity(...) try { repo.save(entity) } catch (e: DataIntegrityViolationException) { // not included: some checks for which constraint failed throw DuplicateUsername(username) // to be handled by the controller }
This does not work when in a @Transactional
method, since the persistence exceptions won't happen until the transaction is commited, which happens outside my service method in the spring transaction wrapper.
EntityManager
before exitingExplicitly call flush
on the EntityManager
at the end of my service methods. This will force the write to the database and as such trigger the exception. However it is potentially inefficient, as I now must take care to not flush multiple times during a request for no reason. I also better not ever forget it or exceptions will disappear into thin air.
Put the @Transactional
methods in a separate spring bean and try-catch around them in the main service. This is weird, as I must take care to do one part of my code in place A and the other in place B.
DataIntegrityViolationException
in the controllerJust... no. The controller has no business (hue hue hue) in handling exceptions from the database.
DataIntegrityViolationException
I have seen several resources on the web, especially in combination with Hibernate, suggesting that catching this exception is wrong and that one should just check the condition before saving (i.e. check if the username exists with a manual query). This does not work in a concurrent scenario, even with a transaction. Yes, you will get consistency with a transaction, but you'll still get DataIntegrityViolationException
when "someone else comes first". Therefor this is not an acceptable solution.
Use Spring's TransactionTemplate
instead of @Transactional
. This is the only somewhat satisfactory solution. However it is quite a bit more "clunky" to use than "just throwing @Transactional
on the method" and even the Spring documentation seems to nudge you towards using @Transactional
.
I would like some advice about how to best handle this situation. Is there a better alternative to my last proposed solution?
@Transactional only rolls back transactions for unchecked exceptions. For checked exceptions and their subclasses, it commits data. So although an exception is raised here, because it's a checked exception, Spring ignores it and commits the data to the database, making the system inconsistent.
In Java EE, exceptions that are raised during the execution of a transactional business method cause the transaction to rollback. However, this is only the case for system exceptions, that is, runtime exceptions, which are not declared in the method signature.
No, the transaction will only be rolled back in case of an uncaught exception. The transactional interceptors "wrap" around the calls of the annotated methods; they cannot see what happens inside them. Hey Haroldo, Thanks for the answer as well as the explanation behind it.
javax.transactionRollbackException exception is thrown when the transaction has been marked for rollback only or the transaction has been rolled back instead of committed. This is a local exception thrown by methods in the UserTransaction , Transaction , and TransactionManager interfaces.
It's very simple, just use the following with @Transactional: So if you throw an Exception or a subclass of it, always use the above with the @Transactional annotation to tell Spring to roll back transactions if a checked exception occurs. Published at DZone with permission of Shamik Mitra, DZone MVB . See the original article here.
This does not work when in a @Transactional method, since the persistence exceptions won't happen until the transaction is commited, which happens outside my service method in the spring transaction wrapper. 2. Flush the EntityManager before exiting Explicitly call flush on the EntityManager at the end of my service methods.
And then Spring is smart enough to transparently handle transactions for you: Any bean’s public method you annotate with the @Transactional annotation, will execute inside a database transaction (note: there are some pitfalls ). So, to get the @Transactional annotation working, all you need to do is this:
@ExceptionHandler annotation provided by Spring Boot can be used to handle exceptions in particular Handler classes or Handler methods. Any method annotated with this is automatically recognized by Spring Configuration as an Exception Handler Method. An Exception Handler method handles all exceptions and their subclasses passed in the argument.
I use the following approach in my project.
public @interface InterceptExceptions { }
<beans ...> <bean id="exceptionInterceptor" class="com.example.ExceptionInterceptor"/> <aop:config> <aop:aspect ref="exceptionInterceptor"> <aop:pointcut id="exception" expression="@annotation(com.example.InterceptExceptions)"/> <aop:around pointcut-ref="exception" method="catchExceptions"/> </aop:aspect> </aop:config> </beans>
import org.aspectj.lang.ProceedingJoinPoint; public class ExceptionInterceptor { public Object catchExceptions(ProceedingJoinPoint joinPoint) { try { return joinPoint.proceed(); } catch (MyException e) { // ... } } }
@Service @Transactional public class SomeService { // ... @InterceptExceptions public SomeResponse doSomething(...) { // ... } }
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