Я пытаюсь понять, как лучше всего обрабатывать постоянные (и потенциально другие) исключения в сочетании с Spring @Transactional.
В этом посте я просто возьму простой пример регистрации пользователя, который может вызвать DataIntegrityViolationException из-за дублирования имени пользователя.
Следующие вещи, которые я пробовал, меня не удовлетворили:
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
}
Это не работает в методе @Transactional, поскольку исключения сохраняемости не будут происходить до тех пор, пока транзакция не будет зафиксирована, что происходит вне моего метода службы в оболочке транзакции Spring.
EntityManager перед выходом.Явно вызываю flush на EntityManager в конце моих методов обслуживания. Это вызовет запись в базу данных и вызовет исключение. Однако это потенциально неэффективно, так как теперь я должен позаботиться о том, чтобы не сбрасывать несколько раз во время запроса без причины. Мне также лучше никогда не забывать об этом, иначе исключения исчезнут в воздухе.
Поместите методы @Transactional в отдельный компонент Spring и попробуйте обработать их в основной службе. Это странно, так как я должен позаботиться о том, чтобы одна часть моего кода была на месте A, а другая - на месте B.
DataIntegrityViolationException в контроллере.Просто нет. Контроллер не занимается обработкой исключений из базы данных (оттенок оттенок оттенок).
DataIntegrityViolationExceptionЯ видел несколько ресурсов в Интернете, особенно в сочетании с Hibernate, предполагающих, что перехват этого исключения неправильный и что нужно просто проверить условие перед сохранением (т.е. проверить, существует ли имя пользователя с помощью ручного запроса). Это не работает в параллельном сценарии даже с транзакцией. Да, вы получите согласованность с транзакцией, но вы все равно получите DataIntegrityViolationException, когда «кто-то другой приходит первым». Поэтому это неприемлемое решение.
Используйте Spring TransactionTemplate вместо @Transactional. Это единственное отчасти удовлетворительное решение. Однако его использование несколько «неуклюже», чем «просто бросить @Transactional в метод», и даже документация Spring, кажется, подталкивает вас к использованию @Transactional.
Я хотел бы посоветовать, как лучше всего справиться с этой ситуацией. Есть ли лучшая альтернатива моему последнему предложенному решению?
Имея ту же проблему, я использую этот вариант (3), звучит как лучший подход. Не забудьте сделать этот второй метод также общедоступным, поскольку он должен быть «аспектированным» (возможно, в зависимости от того, какой фреймворк AOP вы используете).
Привет, вы пробовали использовать @ControlerAdvice? Если нет, возможно, посмотрите здесь.
По сути, это будет пункт 4. Согласно MVC, уровень контроллера не должен иметь дело с исключениями из базы данных.




В своем проекте я использую следующий подход.
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 joinPont)
{
try
{
return joinPont.proceed();
}
catch (MyException e)
{
// ...
}
}
}
@Service
@Transactional
public class SomeService
{
// ...
@InterceptExceptions
public SomeResponse doSomething(...)
{
// ...
}
}
Вы можете использовать класс, аннотированный с помощью @ControllerAdvice или @RestControllerAdvice, для обработки исключений.
Когда контроллер генерирует исключение, вы можете поймать его в этом классе и изменить статус ответа на подходящий или добавить дополнительную информацию об исключении.
Этот метод помогает поддерживать чистый код.
У вас есть множество примеров:
https://www.javainuse.com/spring/boot-exception-handling
https://dzone.com/articles/best-practice-for-exception-handling-in-spring-boo
Проголосовал в (3), например:
@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;
}
}
Почему бы, как вариант (3), не использовать два метода в одном сервисе? Один из них -
@Transactional, другой -DataIntegrityViolationException.