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