Reputation: 1547
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?
Upvotes: 46
Views: 12011
Reputation: 13041
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(...)
{
// ...
}
}
Upvotes: 2
Reputation: 39
Voted to (3),like:
@Service
public class UserExtService extend UserService{
}
@Service
public class UserService {
@Autowired
UserExtService userExtService;
public int saveUser(User user) {
try {
return userExtService.save(user);
} catch (DataIntegrityViolationException e) {
throw DuplicateUsername(username);// GlobalExceptionHandler to response
}
return 0;
}
@Transactional(rollbackFor = Exception.class)
public int save(User user) {
//...
return 0;
}
}
Upvotes: 1
Reputation: 431
You can use a class annotated with @ControllerAdvice or @RestControllerAdvice to handle the exceptions
When a controller throw a exception you can catch it at this class and change the response status to a suitable one or add an extra info of the exception
This method helps you to maintain a clean code
You have numerous examples:
https://www.javainuse.com/spring/boot-exception-handling
https://dzone.com/articles/best-practice-for-exception-handling-in-spring-boo
Upvotes: 0