Вы можете использовать это руководство, чтобы получить простое и практическое понимание того, как работает управление транзакциями в Спринге (Spring) с помощью аннотации @Transacional .
Единственное необходимое условие? Вы должны иметь приблизительное представление об «Эйсид» (ACID), то есть о том, что такое транзакции баз данных и зачем их использовать. Также здесь не рассматриваются распределенные транзакции или реактивные транзакции, хотя общие принципы, с точки зрения Сприга всё же применимы.
Введение
В этом руководстве вы узнаете об основных столпах «структуры абстракции транзакций» (transaction abstraction framework — запутанный термин, не так ли?) ядра Спринга (Spring core) — описанных с большим количеством примеров кода:
-
@Transactional(декларативное управление транзакциями) против программного управления транзакциями. -
Физические и логические транзакции.
-
Интеграция
@TransactionalСпринга и «Джэйпиэй» (JPA) / Гибернэйтом(Hibernate). -
Интеграция
@TransactionalСпринга и Спринг-бута (Spring Boot) или Спринга МВС (Spring MVC). -
Откаты, прокси, общие проблемы и многое другое.
В отличие, скажем, от официальной документации по Спрингу это руководство не запутает вас, погрузившись сразу в тему Spring-first.
Вместо этого вы будете изучать управление транзакциями Спринга нетрадиционным способом: с нуля, шаг за шагом. Это означает, что вы начнёте со старого доброго управления транзакциями СБДЯ (JDBC).
Почему?
Потому что всё, что делает Спринг, построено на этих самых основах СБДЯ (соединение с базами данных на Яве, JDBC). И вы сэкономите кучу времени при работе с аннотацией @Transactional от Спринга, если усвоите эти начала.
Как работает обычное Управление транзакциями СБДЯ (JDBC)
Если вы думаете пропустить этот раздел, не зная транзакций СБДЯ изнутри: не стоит.
Как запускать, фиксировать или откатывать транзакции СБДЯ
Первый важный вывод заключается в следующем: Не имеет значения, используете ли вы аннотацию @Transactional от Спринга, обычный Гибернэйт (Hibernate), «Жук» (jOOQ) или любую другую библиотеку баз данных.
В конечном счёте, все они делают одно и то же — открывают и закрывают (назовём это «управлением») транзакции базы данных. Обычный код управления транзакциями СБДЯ выглядит следующим образом:
import java.sql.Connection; Connection connection = dataSource.getConnection(); // (1) try (connection) { connection.setAutoCommit(false); // (2) // выполнить несколько SQL-запросов... connection.commit(); // (3) } catch (SQLException e) { connection.rollback(); // (4) }
-
Для запуска транзакций необходимо подключение к базе данных. DriverManager.getConnection(url, user, password) тоже подойдёт, хотя в большинстве корпоративных приложений вы будете иметь настроенный источник данных и получать соединения из него.
-
Это единственный способ «начать» транзакцию базы данных в Яве (Java), даже несмотря на то, что название может звучать немного странно.
setAutoCommit(true)гарантирует, что каждый SQL-оператор будет автоматически завёрнут в собственную транзакцию, аsetAutoCommit(false)— наоборот: Вы являетесь хозяином транзакции (транзакций), и Вам придётся начать вызыватьcommitи друзей. Обратите внимание, что флагautoCommitдействует в течение всего времени, пока ваше соединение открыто, что означает, что вам нужно вызвать метод только один раз, а не несколько. -
Давайте зафиксируем нашу транзакцию…
-
Или откатим наши изменения, если произошло исключение.
Да, эти 4 строки — это (в упрощенном виде) всё, что Спринг делает всякий раз, когда вы используете аннотацию @Transactional . В следующей главе вы узнаете, как это работает. Но прежде чем мы перейдём к этому, вам нужно узнать ещё кое-что.
(Небольшое замечание для умников: Библиотеки пулов соединений, такие как HikariCP, могут переключать режим автокоммита автоматически, в зависимости от конфигурации. Но это уже продвинутая тема).
Как использовать уровни изоляции СБДЯ и точки сохранения
Если вы уже играли с аннотацией @Transactional от Спринга, вы могли столкнуться с чем-то подобным:
@Transactional( propagation=TransactionDefinition.NESTED, isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED )
Мы рассмотрим вложенные транзакции Спринга и уровни изоляции позже более подробно, но опять же полезно знать, что все эти параметры сводятся к следующему, основному коду СБДЯ:
import java.sql.Connection; // isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); // (1) // propagation=TransactionDefinition.NESTED Savepoint savePoint = connection.setSavepoint(); // (2) ... connection.rollback(savePoint);
-
Вот как Спринг устанавливает уровни изоляции для соединения с базой данных. Не такое уж хитросплетённый, не так ли?
-
Вложенные транзакции в Спринге — это просто точки сохранения СБДЯ / базы данных. Если вы не знаете, что такое точка сохранения, посмотрите, например, это руководство. Обратите внимание, что поддержка точек сохранения зависит от вашего драйвера СБДЯ/базы данных.
Как работает управление транзакциями в Спринге или Спринг-буте
Поскольку теперь у вас есть хорошее понимание транзакций СБДЯ, давайте посмотрим, как обычный Спринг (ядро, Spring core) управляет транзакциями. Здесь всё применимо 1:1 к Спринг-буту и Спринг-МВС (Spring MVC), но об этом чуть позже…
Что на самом деле представляет собой управление транзакциями Спринга или его (довольно запутанно названная) структура абстракции транзакций?
Помните, что управление транзакциями просто означает: Как Спринг запускает, фиксирует или откатывает транзакции СБДЯ? Звучит ли это как-то знакомо?
Вот в чём загвоздка: В то время как в обычном СБДЯ у вас есть только один способ (setAutocommit(false)) управлять транзакциями, Спринг предлагает вам множество различных, более удобных способов добиться того же самого.
Как использовать «программное управление транзакциями Спринга» (Programmatic Transaction Management)?
Первый, но довольно редко используемый способ определения транзакций в Спринге — программный: Либо через транзакционный шаблон TransactionTemplate, либо непосредственно через PlatformTransactionManager. С точки зрения кода это выглядит следующим образом:
@Service public class UserService { @Autowired private TransactionTemplate template; public Long registerUser(User user) { Long id = template.execute(status -> { // выполнить некоторый SQL, который, например, // вставляет пользователя в базу данных // и возвращает автогенерированный идентификатор return id; }); } }
По сравнению с обычным примером СБДЯ:
-
Вам не нужно возиться с открытием и закрытием соединений с базой данных самостоятельно (
try-finally). Вместо этого вы используете обратные вызовы транзакций. -
Вам также не нужно ловить
SQLExceptions, поскольку Спринг преобразует эти исключения в исключения времени выполнения (runtime exceptions). -
Кроме того, вы лучше интегрируетесь в экосистему Спринга.
TransactionTemplateбудет использоватьTransactionManagerвнутри, который будет использовать источник данных. Все это бобы (бины), которые вы должны указать в конфигурации контекста Спринга, но о которых вам больше не придётся беспокоиться в дальнейшем.
Хотя это и считается незначительным улучшением, программное управление транзакциями — это не то, чем в основном занимается транзакционная структура (transaction framework) Спринга. Вместо этого, все дело в декларативном управлении транзакциями. Давайте выясним, что это такое.
Как использовать декларативное управление транзакциями «Иксэмэль» (XML) от Спринга?
В те времена, когда конфигурация «Иксэмэль» была нормой для проектов Спринга, вы могли конфигурировать транзакции непосредственно в «Иксэмэль». За исключением нескольких старых корпоративных проектов, вы больше не встретите такой подход в природе, поскольку он был вытеснен гораздо более простой аннотацией @Transactional .
В этом руководстве мы не будем подробно останавливаться на конфигурации Иксэмэль, но вы можете использовать этот пример в качестве отправной точки, чтобы углубиться в него — при необходимости (он взят прямо из официальной документации Спринга):
<!-- транзакционный совет (advice) (что "происходит"; см. <aop:advisor/> боб ниже) --> <tx:advice id="txAdvice" transaction-manager="txManager"> <!-- семантика транзакций... --> <tx:attributes> <!--все методы, начинающиеся с 'get', доступны только для чтения --> <tx:method name="get*" read-only="true"/> <!-- другие методы используют настройки транзакции по умолчанию (см. ниже) --> <tx:method name="*"/> </tx:attributes> </tx:advice>
Вы определяете совет АОП (AOP, Aspect Oriented Programming) с помощью приведенного выше блока «Иксэмэль», который затем можно применить к бобу UserService следующим образом:
<aop:config> <aop:pointcut id="userServiceOperation" expression="execution(* x.y.service.UserService.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="userServiceOperation"/> </aop:config> <bean id="userService" class="x.y.service.UserService"/>
Тогда ваш боб UserService будет выглядеть следующим образом:
public class UserService { public Long registerUser(User user) { // выполняем SQL, который, например. // вставляет пользователя в базу данных // и извлекает автогенерированный id return id; } }
С точки зрения кода Явы, этот декларативный подход к транзакциям выглядит намного проще, чем программный подход. Но он приводит к большому количеству сложного, многословного «Иксэмэль» с конфигурациями указателей и советников (advisor).
Таким образом, это приводит к вопросу: Есть ли лучший способ управления декларативными транзакциями вместо «Иксэмэль»? Да, есть: аннотация @Transacional .
Как использовать аннотацию @Transactional Спринга (декларативное управление транзакциями, Declarative Transaction Management)
Теперь давайте посмотрим, как обычно выглядит современное управление транзакциями в Спринге:
public class UserService { @Transactional public Long registerUser(User user) { // выполнить некоторый SQL, который, например, // вставляет пользователя в базу данных // и извлекает автогенерированный id // userDao.save(user); return id; } }
Как это возможно? Больше нет конфигурации «Иксэмэль», а также нет необходимости в другом коде. Вместо этого вам теперь нужно сделать две вещи:
-
Убедитесь, что ваша
ConfigurationСпринга сопровождена аннотацией@EnableTransactionManagementСпринг-буте это будет сделано автоматически). -
Убедитесь, что вы указали менеджер транзакций в вашей
ConfigurationСпринга (это необходимо сделать в любом случае).
И тогда Спринг покажет себя достаточно умным, чтобы явно обрабатывать транзакции для вас: Любой публичный public метод боба, который вы сопровождаете аннотацией @Transactional , будет выполняться внутри транзакции базы данных (обратите внимание: есть некоторые подводные камни).
Итак, чтобы аннотация @Transactional заработала, всё, что вам нужно сделать, это следующее:
@Configuration @EnableTransactionManagement public class MySpringConfig { @Bean public PlatformTransactionManager txManager() { return yourTxManager; // подробнее об этом позже } }
Теперь, когда я говорю, что Спринг явно обрабатывает транзакции для вас. Что это означает на самом деле?
Вооружившись знаниями из примера транзакций СБДЯ, приведённый выше код @Transactional UserService переводится (упрощенно) непосредственно следующим образом:
public class UserService { public Long registerUser(User user) { Connection connection = dataSource.getConnection(); // (1) try (connection) { connection.setAutoCommit(false); // (1) // выполнить некоторый SQL, который, например. // вставляет пользователя в базу данных // и извлекает автогенерированный идентификатор // userDao.save(user); <(2) connection.commit(); // (1) } catch (SQLException e) { connection.rollback(); // (1) } } }
-
Все это просто стандартное открытие и закрытие соединения СБДЯ. Это то, что транзакционная аннотация Спринга делает для вас автоматически, без необходимости писать её явно.
-
Это ваш собственный код, сохраняющий пользователя через ДАО (DAO) или что-то подобное.
Этот пример может выглядеть немного магическим, но давайте посмотрим, как Спринг вставляет этот код соединения для вас.
CGlib и JDK прокси — @Transactional под прикрытием
Спринг не может переписать ваш класс Явы, как я сделал выше, чтобы вставить код подключения (если только вы не используете продвинутые техники, такие как плетение байткода (bytecode weaving), но мы пока опускаем это).
Ваш метод registerUser() действительно просто вызывает userDao.save(user), и нет способа изменить это на лету.
Но у Спринга есть преимущество. По своей сути это контейнер ИК (IoC container). Он создаёт для вас экземпляр UserService и обеспечивает автоподключение этого UserService к любому другому бобу, которому нужен этот UserService.
Теперь, когда вы используете @Transactional над бобами, Спринг использует маленькую хитрость. Он не просто инстанцирует UserService, но и транзакционный прокси этого UserService.
Он делает это с помощью метода под названием proxy-through-subclassing с помощью библиотеки Cglib. Существуют и другие способы построения прокси (например, Dynamic JDK proxies), но пока оставим это на потом.
Давайте посмотрим на прокси в действии на этом рисунке:

Как видно из этой диаграммы, у конкретного прокси есть одна задача.
-
Открытие и закрытие соединений/транзакций с базой данных.
-
А затем делегирование настоящему
UserService, тому, который вы написали. -
А другие бобы, такие как ваш
UserRestController, никогда не узнают, что они разговаривают с прокси, а не с настоящим.
Короткий тест
Посмотрите на следующий исходный код и скажите мне, какой тип UserService автоматически конструирует Спринг, предполагая, что он помечен @Transactional на уровне класса или имеет на уровне метода внутри класса конфигурации.
@Configuration @EnableTransactionManagement public static class MyAppConfig { @Bean public UserService userService() { // (1) return new UserService(); } }
Ответ
Правильно. Спринг создаёт здесь динамический CGLib-прокси вашего класса UserService, который может открывать и закрывать транзакции базы данных для вас. Вы или любые другие бобы даже не заметят, что это не ваш UserService, а прокси, обертывающий ваш UserService.
Для чего вам нужен менеджер транзакций (например, PlatformTransactionManager)?
Теперь не хватает только одной важной части информации, хотя мы уже несколько раз упоминали о ней.
Ваш UserService проксируется на лету, и прокси управляет транзакциями для вас. Но не сам прокси управляет всем этим транзакционным состоянием (открыть, зафиксировать, закрыть), прокси делегирует эту работу менеджеру транзакций.
Спринг предлагает вам интерфейс PlatformTransactionManager / TransactionManager, который по умолчанию поставляется с парой удобных реализаций. Одна из них — менеджер транзакций источника данных.
Он делает то же самое, что вы делали до сих пор для управления транзакциями, но сначала давайте рассмотрим необходимую конфигурацию Спринга:
@Bean public DataSource dataSource() { return new MysqlDataSource(); // (1) } @Bean public PlatformTransactionManager txManager() { return new DataSourceTransactionManager(dataSource()); // (2) }
-
Здесь вы создаёте источник данных, специфичный для базы данных или для пула соединений. В данном примере используется «Майэскуэль» (MySQL).
-
Здесь вы создаёте свой менеджер транзакций, которому нужен источник данных, чтобы иметь возможность управлять транзакциями.
Всё просто. Все менеджеры транзакций имеют методы типа «doBegin» (для запуска транзакции) или «doCommit«, которые выглядят следующим образом — взяты прямо из исходного кода Спринга с некоторым упрощением:
public class DataSourceTransactionManager implements PlatformTransactionManager { @Override protected void doBegin(Object transaction, TransactionDefinition definition) { Connection newCon = obtainDataSource().getConnection(); // ... con.setAutoCommit(false); // Да, вот так.! } @Override protected void doCommit(DefaultTransactionStatus status) { // ... Connection connection = status.getTransaction().getConnectionHolder().getConnection(); try { con.commit(); } catch (SQLException ex) { throw new TransactionSystemException("Could not commit JDBC transaction", ex); } } }
Таким образом, менеджер транзакций источника данных использует при управлении транзакциями точно такой же код, который вы видели в разделе СБДЯ.
Учитывая это, давайте расширим наш рисунок сверху:

Подведем итоги:
-
Если Спринг обнаруживает аннотацию
@Transactionalна бобе, он создаёт динамический прокси этого боба. -
Прокси имеет доступ к менеджеру транзакций и будет просить его открывать и закрывать транзакции/соединения.
-
Сам менеджер транзакций будет просто делать то, что вы делали в разделе «Обычный Ява»: Управлять старым добрым соединением СБДЯ.
В чём разница между физическими и логическими транзакциями?
Представьте следующие два класса транзакций.
@Service public class UserService { @Autowired private InvoiceService invoiceService; @Transactional public void invoice() { invoiceService.createPdf(); // отправка счёта по электронной почте и т.д... } } @Service public class InvoiceService { @Transactional public void createPdf() { // ... } }
UserService имеет транзакционный метод invoice(). Который вызывает другой транзакционный метод, createPdf() на InvoiceService.
Теперь, с точки зрения транзакций базы данных, это должна быть всего лишь одна транзакция базы данных. (Помните: getConnection().setAutocommit(false).commit()). Спринг называет это физической транзакцией, хотя сначала это может показаться немного запутанным.
Однако со стороны Спринга происходит две логические транзакции: Первая — в UserService, вторая — в InvoiceService. Спринг должен быть достаточно умным, чтобы знать, что оба метода @Transacional должны использовать в основе одну и ту же физическую транзакцию базы данных.
Как изменится ситуация при следующем изменении InvoiceService?
@Service public class InvoiceService { @Transactional(propagation = Propagation.REQUIRES_NEW) public void createPdf() { // ... } }
Изменение режима распространения (propagation) на requires_new говорит Спрингу, что createPDF() должна выполняться в собственной транзакции, независимо от любой другой, уже существующей транзакции. Вспоминая раздел данного руководства, посвященный простому Яве, видели ли вы способ «разделить» транзакцию пополам? Я тоже.
Это означает, что ваш код будет открывать два (физических) соединения/транзакции с базой данных. (Снова: getConnection() x2. setAutocommit(false) x2. commit() x2). Спринг теперь должен быть достаточно умным, чтобы две логические транзакции (invoice()/createPdf()) теперь также отображались на две разные физические транзакции базы данных.
Итак, подведем итоги:
-
Физические транзакции: это ваши фактические транзакции СБДЯ.
-
Логические транзакции: это (потенциально вложенные) аннотированные
@Transactionalметоды Спринга.
Это подводит нас к более подробному рассмотрению режимов распространения.
What are @Transactional Propagation Levels used for?
Изучая исходный код Spring, вы найдете множество уровней или режимов распространения, которые можно подключить к методу @Transactional.
@Transactional(propagation = Propagation.REQUIRED) // или @Transactional(propagation = Propagation.REQUIRES_NEW) // и т.д.
Полный перечень:
-
REQUIRED -
SUPPORTS -
MANDATORY -
REQUIRES_NEW -
NOT_SUPPORTED -
NEVER -
NESTED
Упражнение:
В разделе «Простой Ява» я показал вам всё, на что способен СБДЯ, когда речь идёт о транзакциях. Потратьте минуту, чтобы подумать о том, что каждый отдельный режим распространения в Спринге в итоге НА САМОМ ДЕЛЕ делает с вашим источником данных или, скорее, с вашим соединением СБДЯ.
Затем посмотрите на следующие ответы.
Ответы
-
Required (по умолчанию): Моему методу нужна транзакция, либо откройте ее для меня, либо используйте существующую →
getConnection().setAutocommit(false).commit() -
Supports: Мне не важно, открыта транзакция или нет, я могу работать в любом случае → не имеет отношения к СБДЯ
-
Mandatory: Я не собираюсь открывать транзакцию сам, но я буду плакать, если никто другой не откроет её → не имеет отношения к СБДЯ
-
Require_new: Я хочу полностью собственную транзакцию →
getConnection().setAutocommit(false).commit() -
Not_Supported: Мне очень не нравятся транзакции, я даже попытаюсь приостановить текущую, запущенную транзакцию → ничего общего с СБДЯ
-
Never: Я буду плакать, если кто-то другой запустит транзакцию → не имеет отношения к СБДЯ
-
Nested: Это звучит так сложно, но мы просто говорим о точках сохранения! →
connection.setSavepoint()
Как вы можете видеть, большинство режимов распространения на самом деле не имеют ничего общего с базой данных или СБДЯ, но больше с тем, как вы структурируете свою программу со Спригом и как/когда/где Спринг ожидает присутствия транзакций.
Посмотрите на этот пример:
public class UserService { @Transactional(propagation = Propagation.MANDATORY) public void myMethod() { // выполнить sql } }
В этом случае Спринг будет ожидать, что транзакция будет открыта, когда вы вызовете myMethod() класса UserService. Он не открывает её сам, вместо этого, если вы вызовете этот метод без предварительно существующей транзакции, Спринг выбросит исключение. Помните об этом как о дополнительных нюансах для «логической обработки транзакций».
Для чего используются @Transactional уровни изоляции?
Это почти вопрос с подвохом, но что произойдет, если вы настроите аннотацию @Transactional таким образом?
@Transactional(isolation = Isolation.REPEATABLE_READ)
Да, это просто приводит к этому:
connection .setTransactionIsolation( Connection.TRANSACTION_REPEATABLE_READ );
Уровни изоляции базы данных, однако, являются сложной темой, и вам следует потратить некоторое время, чтобы полностью разобраться в них. Хорошим началом является официальная документация Постгрес (Postgres) и ее раздел об уровнях изоляции.
Также обратите внимание, что когда речь идет о переключении уровней изоляции во время транзакции, вы должны обязательно проконсультироваться о вашем драйвере СБДЯ/базы данных, чтобы понять, какие сценарии поддерживаются, а какие нет.
Самый распространенный «подводный камень» @Transactional
Есть один подводный камень, на который обычно наталкиваются начинающие разработчики Спринга. Взгляните на следующий код:
@Service public class UserService { @Transactional public void invoice() { createPdf(); // отправка счета по электронной почте и т.д. } @Transactional(propagation = Propagation.REQUIRES_NEW) public void createPdf() { // ... } }
У вас есть класс UserService с транзакционным методом invoice(). Который вызывает createPDF(), который также является транзакционным.
Сколько физических транзакций, по вашему мнению, будет открыто, когда кто-то вызовет invoice()?
Нет, ответ будет не два, а один. Почему?
Давайте вернемся к разделу «Прокси» этого руководства. Спринг создает для вас транзакционный прокси UserService, но как только вы оказываетесь внутри класса UserService и вызываете другие внутренние методы, прокси больше не задействован. Это означает, что новой транзакции не будет.
Давайте посмотрим на это на рисунке:

Есть несколько приёмов (например, самоинъекция), которые можно использовать, чтобы обойти это ограничение. Но главный вывод: всегда помните о границах транзакций прокси.
Как использовать @Transactional сo Спринг-бутом или Спрингом МВС
До сих пор мы говорили только об обычном, базовом Спринге. Но как насчет Спринг-бута? Или Сприга Вэб МВС? Обрабатывают ли они транзакции по-другому?
Короткий ответ таков: Нет.
В обоих фреймворках (а точнее: во всех фреймворках экосистемы Спринга) вы всегда будете использовать аннотацию @Transactional в сочетании с менеджером транзакций и аннотацией @EnableTransactionManager пути не существует.
Однако единственное отличие Спринг-бута заключается в том, что он автоматически устанавливает аннотацию @EnableTransactionManager и создает PlatformTransactionManager для вас — с помощью автоконфигураций СБДЯ. Подробнее об автоконфигурациях здесь.
Как Спринг обрабатывает откат (и политики отката по умолчанию)
Раздел об откатах Сприг будет рассмотрен в следующей редакции этого руководства.
Как работает управление транзакциями Спринга и «Джейпиэй» (JPA) / Гибернэйта (Hibernate)
Цель: синхронизация @Transactional от Спринга и «Джэйпиэй» (JPA) / Гибернэйта (Hibernate)
В какой-то момент вы захотите, чтобы ваше приложение Спринга интегрировалось с другой библиотекой баз данных, такой как Гибернэйт (популярная реализация «Джэйпиэй«) или «Жук» (Jooq) и т.д.
В качестве примера рассмотрим простой Гибернэйт (примечание: не имеет значения, используете ли вы Гибернэйт напрямую, или Гибернэйт через «Джэйпиэй«).
Переписывание UserService из предыдущей версии в Гибернэйта будет выглядеть следующим образом:
public class UserService { @Autowired private SessionFactory sessionFactory; // (1) public void registerUser(User user) { Session session = sessionFactory.openSession(); // (2) // давайте откроем транзакцию. помните о setAutocommit(false)! session.beginTransaction(); // сохранить == вставить (save == insert) наших объектов session.save(user); // и закрепить его session.getTransaction().commit(); // закрыть сессию == наше соединение СБДЯ session.close(); } }
-
Это обычная, старая
SessionFactoryГибернэйта, точка входа для всех запросов в Гибернэйт. -
Ручное управление сессиями (читай: соединениями с базой данных) и транзакциями с помощью АПИ Гибернэйта (API Hibernate).
Однако в приведённом выше коде есть одна огромная проблема:
-
Гибернэйт не будет знать об аннотации
@Transactionalот Спринга. -
@TransactionalСпринга не будет знать ничего о транзакциях Гибернэйта.
Но нам бы хотелось, чтобы Спринг и Гибернэйт интегрировались без проблем, то есть знали о транзакциях друг друга.
В простом коде:
@Service public class UserService { @Autowired private SessionFactory sessionFactory; // (1) @Transactional public void registerUser(User user) { sessionFactory.getCurrentSession().save(user); // (2) } }
-
Тот же
SessionFactory, что и раньше -
Но больше никакого ручного управления состоянием. Вместо этого
getCurrentSession()и@Transactionalсинхронизируются.Как этого добиться?
Использование HibernateTransactionManager
Существует очень простое решение этой проблемы интеграции:
Вместо использования DataSourcePlatformTransactionManager в конфигурации Спринга, вы будете использовать HibernateTransactionManager (при использовании обычного Hibernate) или JpaTransactionManager (при использовании Гибернэйта через «Джэйпиэй«).
Специализированный HibernateTransactionManager обеспечит:
Управлять транзакциями через Гибернэйт, т.е. через SessionFactory.
Быть достаточно умным, чтобы позволить Спрингу использовать ту же самую транзакцию в коде Спринга, не отвносящемся к Гибернэйту, т.е. @Transactional.
Как всегда, картинка может быть проще для понимания (хотя обратите внимание, что поток между прокси и реальным сервисом является лишь концептуально правильным и упрощенным).

Вот, вкратце, как вы интегрируете Спринг и Гибернэйт.
Для других интеграций или более глубокого понимания поможет беглый взгляд на все возможные реализации PlatformTransactionManager, которые предлагает Спринг.
Заключение
К этому моменту вы должны иметь довольно хорошее представление о том, как управление транзакциями работает с фреймворком Спринг и как оно применяется к другим библиотекам Спринга, таким как Спринг-бут (Spring boot) или Спринг Вэб МВС (Spring WebMVC). Самым главным выводом должно быть то, что в конечном итоге не имеет значения, какой фреймворк вы используете, все дело в основах СБДЯ (JDBC).
Поймите их правильно (помните: getConnection().setAutocommit(false).commit()) и вам будет гораздо легче понять, что произойдет позже в вашем сложном корпоративном приложении.
Спасибо за прочтение.
Благодарности
Спасибо Андреасу Эйзеле за отзывы о ранних версиях этого руководства. Спасибо Бену Хорсфилду за создание очень нужных фрагментов Яваскрипта (Javascript) для улучшения этого руководства.
Переведено с помощью DeepL (бесплатная версия)
ссылка на оригинал статьи https://habr.com/ru/post/682362/
Добавить комментарий