Команда Spring АйО перевела статью об использовании аннотации @Transactional
для управления транзакциями в Spring и в различных фреймворках экосистемы Spring, а также о ее интеграции с Hibernate. После прочтения данной статьи вы будете гораздо лучше понимать основы работы с этой аннотацией, а также механизмы, которые работают под капотом при ее использовании.

Вы можете использовать этот гайд, чтобы разобраться с управлением транзакциями в Spring на простом, практическом уровне с использованием аннотации @Transactional
.
Единственное условие? Вам необходимо иметь хотя бы самое примитивное представление об ACID, то есть о том, что такое транзакции в базах данных и как их использовать. Более того, распределенные транзакции и относительные транзакции здесь не рассматриваются, хотя основные принципы с точки зрения Spring применимы и к ним.
Введение
С помощью этого гайда вы узнаете об основных столпах фреймворка абстрактных транзакций, принадлежащих ядру Spring (странный термин, не правда ли?), который описывается со множеством примеров кода:
@Transactional
(декларативное управление транзакциями) в сравнении с программным управлением транзакциями.Физические и логические транзакции.
Spring
@Transactional
и интеграция в JPA / Hibernate.Spring
@Transactional
и интеграция в Spring Boot / Spring MVC.Откаты (rollback-и), прокси, частые ошибки и многое другое.
В отличие от официальной документации по Spring, к примеру, этот гайд не будет вводить вас в замешательство, сразу погружаясь в тему Spring-first.
Вместо этого вы будете изучать управление транзакциями в Spring нестандартным методом: от истоков, шаг за шагом. Это означает, что начнем мы со старого доброго управления транзакциями в JDBC.
Почему?
Потому что все, что делает Spring, базируется именно на основах JDBC. И вы сэкономите кучу времени в дальнейшем, пользуясь аннотацией @Transactional
от Spring, если сразу постигнете эти основы.
Как работает обычное управление транзакциями в JDBC
Если вы подумываете о том, чтобы пропустить этот раздел, но при этом не знаете транзакции JDBC, как свои пять пальцев, не делайте этого.
Как запускать, коммитить и откатывать JDBC транзакции
Первый важный момент: не имеет значения, используете ли вы аннотацию @Transactional
от Spring, чистый Hibernate, jOOQ или любую другую библиотеку для баз данных.
По сути они все делают одно и тоже, чтобы открыть и закрыть (мы называем это управлением) транзакции в базах данных. Код управления транзакциями в чистом JDBC выглядит вот так:
import java.sql.Connection;
Connection connection = dataSource.getConnection(); // (1)
try (connection) {
connection.setAutoCommit(false); // (2)
// execute some SQL statements...
connection.commit(); // (3)
} catch (SQLException e) {
connection.rollback(); // (4)
}
Вам необходимо подключение к базе данных, чтобы запускать транзакции. DriverManager.getConnection(url, user, password) тоже сработает, хотя в большинстве Enterprise приложений у вас все еще будет сконфигурированный источник данных и вы будете получать подключения от него.
Это единственный способ “начать” транзакцию для базы данных в Java, даже несмотря на то, что название может звучать немного не в тему.
setAutoCommit(true)
гарантирует, что каждый отдельный SQL запрос автоматически оборачивается в свою собственную транзакцию, аsetAutoCommit(false)
делает обратное: вы становитесь господином транзакций, и вам придется самому вызыватьcommit
и остальное. Заметьте себе, что флагautoCommit
остается валидным на все время, пока ваше подключение открыто, что означает, что вам надо вызвать этот метод только один раз, без повторных вызовов.Давайте закоммитим нашу транзакцию…
Или откатим наши изменения, если выбросилось исключение.
Да, эти 4 строчки представляют собой (в предельно упрощенном виде) все, что делает Spring всякий раз, когда вы используете аннотацию @Transactional
. В следующем разделе вы узнаете, как это работает. Но прежде чем мы туда перейдем, еще одна маленькая вещь, о которой вам следует знать.
Небольшое примечание для самых умных: библиотеки пула подключений, такие как HikariCP, могут автоматически переключать для вас режим автокоммита в зависимости от конфигурации. Но это более сложная тема.
Как использовать уровни изоляции JDBC и точки сохранения
Если вы уже поигрались с аннотацией @Transactional
от Spring, вы могли встретиться с чем-то таким:
@Transactional(propagation=TransactionDefinition.NESTED,
isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED)
Мы поговорим о вложенных транзакциях Spring более подробно позже, но опять-таки, будет полезно знать, что эти параметры сводятся к вот такому базовому коду на JDBC:
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 устанавливает уровни изоляции на подключение к базе данных. Не особо мудреная наука, не правда ли?
Вложенные транзакции в Spring — это просто точки сохранения от JDBC / БД. Если вы не знаете, что такое точка сохранения (savepoint), посмотрите например на этот туториал. Заметьте, что поддержка точек сохранения зависит от вашего драйвера JDBC/базы данных.
Как работает управление транзакциями от Spring или Spring Boot
Поскольку теперь вы хорошо понимаете JDBC транзакции, давайте посмотрим на управление транзакциями в чистом ядре Spring. Все, что сказано здесь, применимо один в один к Spring Boot и Spring MVC, но мы поговорим об этом подробнее позже.
Что на самом деле представляет собой управление транзакциями в Spring или его несколько странно названный фреймворк абстрактных транзакций (transaction abstraction framework)?
Запомните, управление транзакциями просто означает следующее: как Spring запускает, коммитит или откатывает JDBC транзакции? Звучит ли это хоть сколько-нибудь знакомо после того, как вы прочли написанное выше?
Вот в чем штука-то: в то время как в чистом JDBC у вас есть только один способ (setAutocommit(false)
) управлять транзакциями, Spring предлагает вам много разных, более удобных способов достичь того же самого.
Как использовать программное управление транзакциями (Programmatic Transaction Management) в Spring?
Первый, довольно редко используемый способ — это задать транзакции в Spring программным путем: либо через TransactionTemplate
, либо напрямую через PlatformTransactionManager
. В коде это выглядит вот так:
@Service
public class UserService {
@Autowired
private TransactionTemplate template;
public Long registerUser(User user) {
Long id = template.execute(status -> {
// execute some SQL that e.g.
// inserts the user into the db and returns the autogenerated id
return id;
});
}
}
По сравнению с примером на чистом JDBC:
Вам не нужно возиться с открытием и закрытием подключений к базе данных (
try
-finally
). Вместо этого вы используете Transaction Callbacks.Вам также нет необходимости отлавливать
SQLException
-ы, поскольку Spring для вашего удобства конвертирует эти исключения в рантайм исключения.И у вас появляется лучшая интеграция в экосистему Spring.
TransactionTemplate
будет вызыватьTransactionManager
под капотом, используя источник данных. Все они являются бинами, которые вам необходимо объявить в конфигурации контекста Spring, но в дальнейшем вы можете больше о них не беспокоиться.
Это является пусть небольшим, но улучшением, однако программное управление транзакциями - это не то, для чего по большей части существует транзакционный фреймворк Spring. Он в основном обеспечивает нам возможность декларативного управления транзакциями (declarative transaction management). Давайте узнаем, что это такое.
Как использовать декларативное управление транзакциями в Spring с помощью XML?
В прежние времена, когда XML-конфигурация была нормой для проектов на Spring, можно было конфигурировать транзакции непосредственно в XML. За исключением пары старых Enterprise проектов, вы больше нигде не встретитесь с таким подходом. Он был заменен на более простую аннотацию @Transactional
.
В этом гайде мы не будем углубляться в детали XML конфигурации, но вам потребуется пример как отправная точка, чтобы изучить этот подход глубже, если он вам понадобится (взято непосредственно из официальной документации по Spring):
<!-- the transactional advice (what 'happens'; see the <aop:advisor/> bean below) -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!-- the transactional semantics... -->
<tx:attributes>
<!-- all methods starting with 'get' are read-only -->
<tx:method name="get*" read-only="true"/>
<!-- other methods use the default transaction settings (see below) -->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
При помощи приведенного выше XML кода вы задаете AOP advice (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) {
// execute some SQL that e.g.
// inserts the user into the db and retrieves the autogenerated id
return id;
}
}
С точки зрения кода на Java, такой подход с декларативными транзакциями выглядит намного проще программного подхода. Но это приводит к появлению сложного XML со срезами кода (pointcut) и конфигурациями адвайзера.
Отсюда возникает вопрос: если ли лучший способ осуществлять декларативное управление транзакциями вместо основанного на XML? Да, он существует: аннотация @Transactional
.
Как использовать декларативное управление транзакциями в Spring с помощью аннотации @Transactional?
Давайте теперь посмотрим на то, как обычно выглядит в Spring современный способ управления транзакциями:
public class UserService {
@Transactional
public Long registerUser(User user) {
// execute some SQL that e.g.
// inserts the user into the db and retrieves the autogenerated id
// userDao.save(user);
return id;
}
}
Как это возможно? Конфигурации на XML больше нет, но нет и необходимости использования другого кода. Вместо этого теперь надо сделать две вещи:
Убедитесь в том, что ваша Spring-конфигурация аннотирована
@EnableTransactionManagement
(в Spring Boot это будет сделано автоматически за вас).Убедитесь в том, что вы объявили менеджер транзакций в вашей Spring-конфигурации (это вам придется сделать в любом случае).
Spring достаточно умен, чтобы прозрачно управлять транзакциями за вас: любой публичный метод бина, помеченный аннотацией
@Transactional
, будет выполнен внутри одной транзакции БД (у такого подхода есть и недостатки).
Поэтому, чтобы аннотация @Transactional
заработала, вам необходимо сделать только вот это:
@Configuration
@EnableTransactionManagement
public class MySpringConfig {
@Bean
public PlatformTransactionManager txManager() {
return yourTxManager; // more on that later
}
}
Когда я говорю, что Spring прозрачно управляет транзакциями за вас, что это означает на самом деле?
Вооруженный познаниями из приведенного выше примера транзакции на JDBC, приведенный выше код @Transactional
UserService
превращается (проще говоря) вот в это:
public class UserService {
public Long registerUser(User user) {
Connection connection = dataSource.getConnection(); // (1)
try (connection) {
connection.setAutoCommit(false); // (1)
// execute some SQL that e.g.
// inserts the user into the db and retrieves the autogenerated id
// userDao.save(user); <(2)
connection.commit(); // (1)
} catch (SQLException e) {
connection.rollback(); // (1)
}
}
}
Это просто стандартное открытие и закрытие JDBC подключения. Это то, что транзакционная аннотация от Spring делает за вас автоматически, чтобы вам не пришлось писать это в явном виде.
Это ваш код, сохранение пользователя через DAO или что-то наподобие.
Этот пример может выглядеть как магия, но давайте посмотрим на то, как Spring вставляет для вас этот код для подключений.
CGlib и JDK прокси — @Transactional под капотом
На самом деле Spring не может переписать ваш код на Java, как я это сделал выше, чтобы вставить код для подключений (если только вы не используете продвинутые технологии типа bytecode weaving, но пока проигнорируем это).
Ваш метод registerUser()
на самом деле просто вызывает userDao.save(user)
, это нельзя поменять на лету.
Но у Spring есть преимущество. Его ядро по сути является IoC контейнером. Он инстанциирует для вас UserService
и проделывает операцию autowire
на UserService
, включая его в любой бин, которому нужен UserService
.
Каждый раз, когда вы используете @Transactional
на бине, Spring пользуется маленьким трюком. Он инстанциирует не только UserService
, но также и транзакционный прокси этого UserService
.
Это происходит через метод, называемый proxy-through-subclassing с помощью библиотеки Cglib. Есть также другие способы создавать прокси (например, Dynamic JDK proxies), но пока что давайте оставим их в покое.
Давайте посмотрим на прокси в действии:

Как вы можете видеть из этой схемы, у прокси есть только одна задача:
Открыть и закрыть подключение к базе данных / транзакцию.
И затем делегировать настоящему
UserService
, тому, который вы написали.И остальные бины, такие как ваш
UserRestController
, никогда не узнают, что они общаются с прокси, а не с настоящимUserService
.
Быстрая проверка знаний
Посмотрите на следующий исходный код и скажите мне, какой тип UserService
автоматически создается Spring-ом, исходя из предположения, что он помечен как @Transactional
или содержит @Transactional
метод.
@Configuration
@EnableTransactionManagement
public static class MyAppConfig {
@Bean
public UserService userService() { // (1)
return new UserService();
}
}
Правильно. Spring создает динамический CGLib прокси вашего UserService
класса, который может открывать и закрывать транзакции для вас. Вы или любой другой бин даже не заметите, что это не ваш UserService
, а прокси, оборачивающий ваш UserService
.
Для чего вам нужен менеджер транзакций (например, PlatformTransactionManager)?
У нас остался лишь один важный для понимания момент, о котором мы уже упоминали пару раз.
Ваш UserService
передается через прокси на лету, и этот прокси осуществляет управление транзакциями за вас. Но не сам прокси занимается управлением состояниями транзакций (открываем, коммитим, закрываем), прокси делегирует эту работу менеджеру транзакций.
Spring предлагает вам интерфейсы PlatformTransactionManager
/ TransactionManager
, которые по умолчанию поступают к вам уже с парочкой удобных реализаций. Одна из них — это менеджер транзакций для источника данных.
Он делает то же самое, что вы до сих пор делали сами для управления транзакциями, но сначала давайте посмотрим на нужную нам конфигурацию Spring:
@Bean
public DataSource dataSource() {
return new MysqlDataSource(); // (1)
}
@Bean
public PlatformTransactionManager txManager() {
return new DataSourceTransactionManager(dataSource()); // (2)
}
Вы создаете пул подключений, специфичный для используемого здесь источника данных. Для данного примера используется MySQL.
Здесь вы создаете ваш менеджер транзакций, которому нужен источник данных, чтобы получить возможность управлять транзакциями.
Все просто. Все менеджеры транзакций после этого получают методы типа "doBegin
" (для запуска транзакций) или "doCommit
", который выглядит вот так (взято напрямую из исходного кода Spring и немного упрощено):
public class DataSourceTransactionManager implements PlatformTransactionManager {
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
Connection newCon = obtainDataSource().getConnection();
// ...
con.setAutoCommit(false);
// yes, that's it!
}
@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);
}
}
}
Таким образом, менеджер транзакций для источника данных использует в точности тот же самый код, который вы видели в разделе о JDBC, когда управляет транзакциями.
С учетом данного факта, давайте несколько улучшим предыдущую картинку:

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

Существуют некоторые трюки (например, self-injection), которые вы можете использовать, чтобы обойти это ограничение. Но главный вывод состоит в следующем: не забывайте об ограничениях транзакций внутри прокси.
Как использовать @Transactional со Spring Boot и Spring MVC
До сих пор мы говорили только о чистом Spring. Но как насчет Spring Boot? Или Spring Web MVC? Работают ли они с транзакциями как-то иначе?
Ответ: нет.
С любым из этих двух фреймворков (а точнее, со всеми фреймворками в экосистеме Spring) вы всегда будете использовать аннотацию @Transactional
в сочетании с менеджером транзакций и аннотацией @EnableTransactionManagement
. Другого способа нет.
Однако, есть одно отличие для случая Spring Boot, который автоматически подставляет аннотацию @EnableTransactionManagement
и создает для вас PlatformTransactionManager
с его JDBC автоконфигурациями. Вы можете узнать больше об автоконфигурациях здесь.
Как Spring управляет откатами (и политиками откатов по умолчанию)
Раздел об откатах в Spring будет написан в следующей версии данного гайда.
Как работает управление транзакциями в Spring с JPA / Hibernate
Цель: синхронизировать @Transactional от Spring и Hibernate / JPA
В какой-то момент вы захотите интегрировать ваше Spring приложение с другой библиотекой по базам данных, такой как Hibernate (популярная реализация JPA), Jooq и т.д.
Давайте возьмем чистый Hibernate в качестве примера (примечание: не имеет значение, используете ли вы Hibernate напрямую или через JPA).
Чтобы переписать наш прежний UserService
под Hibernate, мы могли бы сделать так:
public class UserService {
@Autowired
private SessionFactory sessionFactory; // (1)
public void registerUser(User user) {
Session session = sessionFactory.openSession(); // (2)
// lets open up a transaction. remember setAutocommit(false)!
session.beginTransaction();
// save == insert our objects
session.save(user);
// and commit it
session.getTransaction().commit();
// close the session == our jdbc connection
session.close();
}
}
Это
SessionFactory
из чистого старого Hibernate, точка входа для всех Hibernate запросов.Управление вручную сессиями (в данном случае подключениями к базе данных), а также транзакциями с помощью Hibernate’s API.
Однако, в приведенном выше коде есть одна огромная проблема:
Hibernate ничего не будет знать об аннотации
@Transactional
от Spring.@Transactional
от Spring ничего не будет знать о транзакции от Hibernate.
Но нам так хотелось бы, чтобы Spring и Hibernate гладко интегрировались между собой, что означает, что они должны знать о транзакциях друг друга.
Говоря языком простого кода:
@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 в вашей конфигурации Spring, вы будете использовать HibernateTransactionManager (если используете чистый) или JpaTransactionManager (если используете Hibernate через JPA).
Специализированный HibernateTransactionManager
обеспечит следующее:
Управление транзакциями через Hibernate, то есть
SessionFactory
.Он будет достаточно сообразительным, чтобы позволить Spring использовать ту же самую транзакцию в коде, который не относится к Hibernate, то есть Spring код с
@Transactional
.
Как обычно, картинка может быть проще в понимании (однако отметьте для себя, что алгоритм общения между прокси и настоящим сервисом верен только концептуально и предельно упрощен).

Все вышесказанное вкратце описывает то, как вы интегрируете Spring с Hibernate.
Для других интеграций и для более глубокого понимания имеет смысл посмотреть на все возможные реализации PlatformTransactionManager, предлагаемые Spring.
Заключение
К текущему моменту у вас должно быть довольно неплохое понимание того, как работает управление транзакциями во фреймворке Spring и как эти принципы применяются в других библиотеках Spring, таких как Spring Boot или Spring WebMVC. Главным выводом должно стать то, что не важно, какой фреймворк вы используете, в конце концов все сводится к базовым понятиям JDBC.
Настройте все правильно (помните о getConnection(). setAutocommit(false). commit()
.), и у вас появится гораздо лучшее понимание того, что произойдет позднее в вашем комплексном Enterprise приложении.
Спасибо за внимание.

Присоединяйтесь к русскоязычному сообществу разработчиков на Spring Boot в телеграм — Spring АйО, чтобы быть в курсе последних новостей из мира разработки на Spring Boot и всего, что с ним связано.
ris58h
Странно, что в статье с таким названием ничего нет про про rollbackFor/noRollbackFor.