Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling exceptions during a @Transactional method in Spring

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?

like image 693
diesieben07 Avatar asked May 03 '18 08:05

diesieben07


People also ask

Does @transactional throw exception?

@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.

How do you handle transactional exceptions?

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.

What happens if an exception is thrown in the method annotated transactional?

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.

How do you handle transaction rollback exception?

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.

How do I rollback a spring transaction if an exception occurs?

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.

Why @transactional method does not work with persistence exceptions?

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.

How do you handle transactions in spring?

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:

What is @exceptionhandler annotation in Spring Boot?

@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.


Video Answer


1 Answers

I use the following approach in my project.

  1. Custom annotation.
public @interface InterceptExceptions { } 
  1. Bean and aspect at spring context.
<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)     {       // ...     }   }   } 
  1. And finally, usage.
@Service @Transactional public class SomeService {   // ...    @InterceptExceptions   public SomeResponse doSomething(...)   {     // ...   } } 
like image 112
SternK Avatar answered Sep 22 '22 23:09

SternK