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:
1. Naive approach: Just catch the Exception
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.
2. Flush the EntityManager before exiting
Explicitly 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.
3. Make two service classes
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.
4. Handle DataIntegrityViolationException in the controller
Just... no. The controller has no business (hue hue hue) in handling exceptions from the database.
5. Don't catch 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.
7. Do not use declarative transaction management
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, the other catching theDataIntegrityViolationException. - Markus Pscheidt