22 февраля 2020
Тэги: Spring Data Spring Java 10
Транзакцией называется набор связанных операций, все из которых должны быть выполнены корректно без ошибок. Если при выполнении одной из операций возникла ошибка, все остальные должны быть отменены. Прежде всего такой механизм применяется при работе с БД.
Spring предлагает очень простой декларативный способ управления транзакциями. Вам достаточно добавить @org.springframework.transaction.annotation.Transactional к публичному сервисному методу, и все операции внутри этого метода будут выполняться в транзакции. При выходе из метода транзакция будет завершена (операция commit в терминах БД) автоматически. Если в процессе работы возникнет исключение и оно не будет перехвачено внутри метода, транзакция будет отменена (операция rollback) и все данные вернуться в то состояние, в котором они были до начала транзакции.
Предположим, у нас есть метод в спринговом сервисе, который выполняет несколько запросов в БД. Для простоты можно использовать Spring Data, чтобы оперировать записями в БД в ООП стиле.
Сама сущность, которую мы сохраняем в базу, имеет следующий вид:
Слой dao, представляющий базовые методы для сохранения данной сущности в БД выглядит так:
Теперь создадим метод в сервисном слое, который сначала создаёт сущность, сохраняет её в БД, затем обновляет её имя. То есть сначала в БД происходит запрос insert, затем update.
Сервисный метод вызывается из контроллера при помощи POST-запроса:
Данный код всегда работает без ошибок. А теперь добавим между созданием и обновлением записи в БД ошибку.
Если мы выполним данный код, то увидим, что новая запись была добавлена в таблицу, но до обновления дело уже не дошло. Чтобы держать наши данные согласованными, достаточно добавить к методу вышеупомянутую аннотацию:
В данном случае первый запрос будет выполнен, а потом отменён и в таблице в БД новых записей так и не появится.
Бывают такие ошибки, при которых откатывать транзакцию не требуется. Эти исключения вы можете перечислить при помощи параметра dontRollbackOn:
Если мы сделаем так для нашего примера, то запись всё-таки будет создана, затем возникнет исключение, обновления уже не произойдёт, но транзакция всё равно будет завершена.
Как Spring реализует механизм создания транзакций? Важно отметить, что @Transactional будет работать только для публичных методов, вызываемых из других компонентов. Это происходит потому что спринг делает вызов целевого метода через прокси-объект, имеющий такой же интерфейс, как и наш сервис. В этом прокси-объекте происходит открытие транзакции перед вызовом целевого метода. Затем, после завершения целевого метода, происходит закрытие транзакции. Если из метода вылетит исключение, транзакция будет отменена. Это поведение похоже на конструкцию try-catch. Можно представлять себе логику работы прокси так:
Если вы повесите @Transactional на какой-либо метод и вызовете его из того же сервиса, но из другого метода, механизм работать не будет, т.к. вызов не проходит через прокси. Это надо иметь в виду.
Kotlin, Java, Java 16, Java 11, Java 10, Java 9, Java 8, Spring, Spring Boot, Spring Data, SQL, PostgreSQL, Oracle, Hibernate, Collections, Stream API, многопоточность, ввод-вывод, Apache, maven, gradle, JUnit, ООП, алгоритмы, головоломки, rest, GraphQL, Excel, XML, json, yaml