transactional spring что это
@Transactional в Spring под капотом
В этой статье рассматривается как работает аннотация @Transactional в Spring под капотом. При этом в основном рассматривается НЕ реактивный стек. Статья рассматривает код Spring 5.3 и Spring Boot 2.4
Оглавление
Где лежит @Transactional и как его добавить в проект?
Кратко о том как работает proxy или самый популярный вопрос на собеседовании
Как выводить логи транзакций?
Императивная работа с транзакциями (через TransactionTemplate)
Обработка ошибок в HibernateTransactionManager
Где лежит @Transactional и как его добавить в проект?
Для того, чтобы добавить в проект пакет, требуется прописать зависимость:
Кто создает инфраструктуру для обработки @Transactional?
Эта аннотация имеет следующие настройки:
Что же эта аннотация делает? Посмотрим на нее:
С приходом Spring Boot необходимость в аннотации @EnableTransactionManager отпала. Теперь это перешло в ответственность Spring Boot. Как же он это делает?
которая будет загружена при подъеме контекста. Полный текст для удобства приведен ниже.
Как работают авто-конфигурации хорошо объяснено в «SpringBoot-потрошитель», здесь упомяну только наиболее интересные моменты.
Наиболее важной частью является статический класс EnableTransactionManagementConfiguration
Он содержит два подкласса, отличаются они только настройкой
Обратите внимание, что по умолчанию для всех современных spring-boot проектов
Поэтому будет использован механизм CGLIB для создания proxy. (Советую посмотреть интервью, где Борисов рассказывает про это изменение поведения Spring Boot).
Кто обрабатывает @Transactional?
В прошлой части мы закончили на
Посмотрим, что происходит дальше.
Здесь используются следующие классы для конфигураций:
Этот bean применяется для проверки: используется ли где-то @Transactional и для получения метаданных (например, propagation) для аннотированных методов или классов.
Иерархия наследования для класса InfrastructureAdvisorAutoProxyCreator
Обработка @Transactional выполняется по обычным правилам Spring и никакой особой магии здесь нет. Примерная схема работы (взята из официальной документации)
Примерная схема работы @Transactional
Кратко о том как работает proxy или самый популярный вопрос на собеседовании
Во многих фирмах, где проходил собеседование задавали следующий вопрос:
Будет ли при вызове test2 из метода test1 создана новая транзакция?
Самый грамотный и полный ответ, который я встречал, был уже приведен в этой статье на habr. В этой же статье рассмотрим только классический и самый простой случай для лучшего понимания, что происходит дальше.
Под капотом наш bean будет иметь примерно следующий вид
Это прекрасно иллюстрирует этот рисунок
взято с https://www.javainuse.com/spring/spring-boot-aop
Как обрабатываются @Transactional
Прежде чем перейти к рассмотрению порядка обработки, давайте посмотрим, какие настройки предоставляет нам эта аннотация. Здесь рассмотрена только часть из них. Остальные можно посмотреть в этой статье на habr или в документации
Наиболее интересные настройка @Transactional
Выделяется следующие способы:
Правила управления откатом
Код взят из статьи
Порядок работы такой:
выполняем необходимые запросы
Сокращенный код приведен ниже
Если посмотреть внимательно, то этот код повторяет предыдущую логику. Ничего нового:
Рассмотрим внимательно каждую часть.
Управление откатом изменений
Просто выполняется фиксация, если транзакция активна.
Рассмотрим внимательней последний.
Стоит отметить, что в своей работе AbstractPlatformTransactionManager и его подклассы активно используют org. springframework.transaction.support.TransactionSynchronizationManage для синхронизации и хранения метаинформации, включая connection. Хранение информации осуществляется в наборе статических ThreadLocal переменных.
Как выводить логи транзакций?
Также можно узнать, что происходит с транзакциями программно, обращаясь к ThreadLocal переменным, которые мы рассмотрели, например,
Императивная работа с транзакциями (через TransactionTemplate)
который повторяет стандартный механизм rollback/commit:
получаем транзакцию (getTransaction)
выполняем действие (doInTransaction)
если была ошибка, откатываемся (rollbackOnException)
если все хорошо, то фиксируем транзакцию (commit)
Интересна строчка начала транзакции
Обработка ошибок в HibernateTransactionManager
При работе с hibernate, даже если вы поймали ошибку и обработали ее, вы не сможете уже зафиксировать транзакцию, так как она будет помечаться как rollbackOnly (в отличие от работы с JdbcTemplate, например). Как это работает и почему?
Как это работает? Если hibernate ловит ошибку, внутри себя он вызывает
При вызове PlatformTransactionManager.commit получаем статус транзакции, который внутри хранит флаг:
doCommit все равно вызывается и мы могли бы ожидать, что хоть что-то зафиксируется, но hibernate не имеет теперь консистентных данных, поэтому внутри hibernate есть такой код
То есть ответственность за откат здесь переходит на сторону Hibernate, а не Spring, хотя Spring и не вызывает PlatformTransactionManager.rollback
Использованные материалы
Раскрытие секретов о Spring Boot от Борисова
Пример работы c AspectJ
Пример использования Pointcut
Еще один пример использования Pointcut
Spring patterns для взрослых
Spring AOP. Маленький вопросик с собеседования
Эффективное управление транзакциями в Spring
Справка по @Transactional
Introducation to Transactional in Java and Spring
Spring @Transactional — ошибки, которые совершали все
В этой статье я собрал проблемы, с которыми лично сталкивался в проектах. Надеюсь, этот список поможет вам лучше понять транзакции и поспособствует устранению нескольких ваших замечаний.
1. Вызовы в пределах одного класса
@Transactional редко подвергается достаточному количеству тестов, и это приводит к тому, что какие-то проблемы не видны на первый взгляд. В результате вы можете столкнуться со следующим кодом:
Аннотация не работает в методе registerAccount:
В этом случае при вызове registerAccount() сохранение пользователя и создание команды не будут выполняться в одной транзакции. @Transactional работает на основе аспектно-ориентированного программирования. Поэтому обработка происходит, когда один бин вызывается из другого. В приведенном выше примере метод вызывается из того же класса, поэтому прокси не могут быть применены. Так происходит и с другими аннотациями, как, к примеру, @Cacheable.
Проблема может быть решена тремя основными способами:
Самостоятельная инъекция (Self-inject)
Создать еще один уровень абстракции
Первый способ кажется менее очевидным, но таким образом мы избегаем дублирования логики, если @Transactional содержит параметры.
Аннотация работает в методе registerAccount:
2. Обработка не всех исключений
По умолчанию откат происходит только при RuntimeException и Error. В то же время в коде могут встречаться проверяемые исключения, при которых также необходимо откатить транзакцию.
Установите rollbackFor , если вам нужно откатиться назад в случае StripeException:
3. Уровни изоляции транзакций и распространение
Понимание уровней изоляции необходимо для того, чтобы избежать ошибок, которые потом очень трудно отлаживать.
Например, если вы создаете отчеты, то можно выбрать разные данные на уровне изоляции по умолчанию, выполнив один и тот же запрос несколько раз в течение транзакции. Это происходит, когда параллельная транзакция фиксирует что-то в это время. Использование REPEATABLE_READ поможет избежать таких сценариев и сэкономить массу времени на поиск и устранение неисправностей.
4. Транзакции не блокируют данные
Иногда возникает такая конструкция, когда мы выбираем что-то в базе данных, затем обновляем это, и думаем, что поскольку все это делается в транзакции, а транзакции обладают свойством атомарности, то этот код выполняется как один запрос.
Однако проблема в том, что ничто не мешает другому экземпляру приложения вызвать findAllByStatus одновременно с первым. В результате метод вернет одни и те же данные в обоих экземплярах, и их обработка будет произведена 2 раза.
Есть 2 способа избежать этой проблемы.
Выбрать для обновления (пессимистическая блокировка)
Select-for-update в PostgreSQL:
В приведенном выше примере, когда выполняется выбор, строки блокируются до конца обновления. Запрос возвращает все измененные строки.
Версионирование сущностей (оптимистическая блокировка)
5. Два разных источника данных
Например, мы создали новую версию хранилища данных, но все еще должны некоторое время поддерживать старую.
Конечно, в этом случае только один save будет обрабатываться транзакционно, именно в том TransactionalManager, который используется по умолчанию.
Spring предоставляет здесь два варианта.
ChainedTransactionManager
ChainedTransactionManager — это способ объявления нескольких источников данных, в которых, в случае исключения, откат будет происходить в обратном порядке. Таким образом, при наличии трех источников данных, если во время фиксации на втором произошла ошибка, то откат будет произведен только для первых двух. Третий уже зафиксировал изменения.
JtaTransactionManager
Этот менеджер позволяет использовать полностью поддерживаемые распределенные транзакции на основе двухфазной фиксации. Однако он делегирует управление бэкенд-провайдеру JTA. Это могут быть серверы Java EE или отдельные решения (Atomikos, Bitrionix и т.д.).
Заключение
Транзакции — непростая тема, и нередко возникают проблемы с их пониманием. Чаще всего они не полностью покрываются тестами, так что большинство ошибок можно заметить только при ревью кода. А если в продакшне случаются инциденты, найти первопричину всегда непросто.
Всех желающих приглашаем на открытый урок «Репликация». На занятии мы:
— Рассмотрим принцип работы механизмов репликации с точки зрения синхронизации данных;
— Проанализируем проблемы асинхронной репликации и варианты их решения;
— Обсудим предназначение и потенциальные проблемы репликации вида master-master, а также рассмотрим преимущества и недостатки безмастерной репликации.
Как Spring @Transactional действительно работает?
В этом посте мы углубимся в управление транзакциями Spring. Мы рассмотрим, как @Transactional действительно работает под капотом. Другие предстоящие сообщения будут включать в себя:
JPA и управление транзакциями
Важно отметить, что JPA сама по себе не обеспечивает какого-либо декларативного управления транзакциями. При использовании JPA вне контейнера внедрения зависимостей, транзакции должны обрабатываться разработчиком программно:
Этот способ управления транзакциями делает область транзакции очень ясной в коде, но имеет несколько недостатков:
Использование Spring @Transactional
В Spring @Transactional приведенный выше код сводится к следующему:
Это намного удобнее и удобочитаемее, и в настоящее время это рекомендуемый способ обработки транзакций в Spring.
Одним из потенциальных недостатков является то, что этот мощный механизм скрывает то, что происходит под капотом, затрудняя отладку, когда что-то не работает.
Одним из ключевых моментов в @Transactional является то, что нужно рассмотреть две отдельные концепции, каждая со своей областью действия и жизненным циклом:
Контекст постоянства — это просто объект синхронизатора, который отслеживает состояние ограниченного набора объектов Java и гарантирует, что изменения в этих объектах в конечном итоге сохраняются обратно в базу данных.
Когда EntityManager охватывает несколько транзакций базы данных?
В таком случае запросы, которые выполняются на уровне представления, находятся в отдельных транзакциях базы данных, чем те, которые используются для бизнес-логики, но они выполняются через один и тот же менеджер сущностей.
Что определяет отношение EntityManager против транзакции?
Это на самом деле выбор разработчика приложения, но наиболее распространенный способ использования JPA Entity Manager — это шаблон «Entity Manager для каждой транзакции приложения». Это наиболее распространенный способ внедрения менеджера сущностей:
Распространение транзакций и изоляция весной @Transactional
Подробнее об условиях изоляции и распространения можно узнать в @Transactional
Распространение транзакций и изоляция весной @Transactional
1. Введение
В этом учебнике мы покроем @Transactional аннотация и ее изоляция и распространение Параметры.
2. Что такое @Transactional?
Мы можем использовать @Transactional обернуть метод в транзакцию базы данных.
Это позволяет нам устанавливать условия распространения, изоляции, тайм-аута, только для чтения и отката для нашей транзакции. Также можно указать менеджера транзакций.
2.1. @Transactional подробности осуществления
Spring создает прокси или манипулирует кодом класса для управления созданием, коммитом и откатом транзакции. В случае прокси, Весна игнорирует @Transactional во внутренних вызовов метода.
Проще говоря, если у нас есть метод, как callMethod и мы отмечаем его как @Transactional, Весна обернет некоторый код управления транзакцией вокруг призыва: @Transactional метод под названием:
2.2. Как использовать @Transactional
Мы можем поместить аннотацию на определения интерфейсов, классов или непосредственно на методы. Они переопределяют друг друга в соответствии с приоритетом порядка; от самого низкого до самого высокого у нас есть: интерфейс, суперкласс, класс, метод интерфейса, метод суперкласса и метод класса.
Однако, если мы ставим аннотацию на частный или защищенный метод, Spring проигнорирует ее без ошибки.
Начнем с образца интерфейса:
Как правило, не рекомендуется устанавливать @Transactional на интерфейсе. Тем не менее, это приемлемо для таких случаев, как @Repository с весенними данными.
Мы можем поместить аннотацию в определение класса, чтобы переопределить параметр транзакции интерфейса/суперкласса:
Теперь давайте переопределим его, установив аннотацию непосредственно на методе:
3. Распространение транзакций
Распространение определяет границу транзакций нашей бизнес-логики. Spring удается начать и приостановить транзакцию в соответствии с нашими распространение оправа.
Теперь давайте рассмотрим различные распространения и как они работают.
3.1. РАСПРОСТРАНЕНИЕ REQUIRED
ОБЯЗАТЕЛЬНЫе является распространением по умолчанию. Весенние проверки, если есть активная транзакция, то она создает новую, если ничего не существовало. В противном случае бизнес-логика придатки к в настоящее время активной транзакции:
Кроме того, ОБЯЗАТЕЛЬНЫе является распространением по умолчанию, мы можем упростить код, сбросив его:
Давайте посмотрим псевдо-код того, как создание транзакций работает для ОБЯЗАТЕЛЬНЫе распространение:
3.2. Пропаганда SUPPORTS
Давайте посмотрим псевдокодей создания транзакции для SUPPORTS :
3.3. Распространение MANDATORY
И давайте еще раз посмотрим псевдо-код:
3.4. НИКОГДА не распространяется
Для транзакционной логики с НИКОГДА распространение, Весна бросает исключение, если есть активная транзакция:
Давайте посмотрим псевдо-код того, как создание транзакций работает для НИКОГДА распространение:
3.5. NOT_SUPPORTED Распространение
Весна сначала приостанавливает текущую транзакцию, если она существует, затем бизнес-логика выполняется без транзакции.
тем JTAТрансакцияМенагер поддерживает реальную приостановку транзакций из коробки. Другие моделируют подвеску, удерживая ссылку на существующую, а затем очищая ее из контекста потока
3.6. REQUIRES_NEW Распространение
И псевдо-код выглядит так:
3.7. Распространение NESTED
ДанныеИсточникТрансакцияМенагер поддерживает это распространение из коробки. Кроме того, некоторые реализации JTAТрансакцияМенагер может поддержать это.
Наконец, давайте распространение НЕ ВЛОЖЕННЫЕ :
4. Изоляция транзакций
Изоляция является одним из общих свойств ACID: атомачность, последовательность, изоляция и долговечность. Изоляция описывает, как изменения, применяемые параллельными транзакциями, видны друг другу.
Каждый уровень изоляции предотвращает нулевые или более побочные эффекты транзакции:
4.1. Управление изоляцией весной
Мы должны также рассмотреть случаи, когда мы называем цепочку методов с различной изоляцией . В обычном потоке изоляция применяется только при новой транзакции. Таким образом, если по какой-либо причине мы не хотим, чтобы метод для выполнения в другой изоляции, мы должны установить TransactionManager:setValidateExistingTransaction к истине. Тогда псевдо-код проверки транзакций будет:
Теперь давайте глубоко в различных уровнях изоляции и их последствия.
4.2. READ_UNCOMMITTED Изоляция
READ_UNCOMMITTED является самым низким уровнем изоляции и обеспечивает наиболее параллельный доступ.
В результате, он страдает от всех трех упомянутых побочных эффектов concurrency. Таким образом, транзакция с этой изоляцией считывает незавершенные данные других параллельных транзакций. Кроме того, как не повторяемые, так и фантомные чтения могут произойти. Таким образом, мы можем получить другой результат при повторном считыив строку или повторное выполнение запроса диапазона.
Мы можем установить изоляция уровень для метода или класса:
Postgres не поддерживает READ_UNCOMMITTED изоляции и падает обратно в READ_COMMITED место . Кроме того, Oracle не поддерживает и не READ_UNCOMMITTED .
4.3. READ_COMMITTED Изоляция
Второй уровень изоляции, READ_COMMITTED, предотвращает грязные читает.
Остальные побочные эффекты concurrency все еще могут случиться. Таким образом, незавершенные изменения в параллельных транзакциях не влияют на нас, но если транзакция совершает свои изменения, наш результат может измениться путем повторного запроса.
Здесь мы устанавливаем изоляция уровень:
READ_COMMITTED является уровнем по умолчанию с Postgres, сервером S’L и Oracle.
4.4. REPEATABLE_READ Изоляция
Третий уровень изоляции, REPEATABLE_READ, предотвращает грязные и не повторяемые чтения. Таким образом, на нас не влияют незавершенные изменения в параллельных транзакциях.
Кроме того, когда мы повторно запрос на строку, мы не получаем другой результат. Но при повторном выполнении диапазон-запросов мы можем получить недавно добавленные или удаленные строки.
Кроме того, это самый низкий требуемый уровень для предотвращения потерянного обновления. Потерянное обновление происходит, когда две или более одновременных транзакций считыв и обновляют один и тот же ряд. REPEATABLE_READ вообще не допускает одновременного доступа к строке. Таким образом, потерянное обновление не может произойти.
Вот как установить изоляция уровень для метода:
REPEATABLE_READ является уровень по умолчанию в Mysql. Oracle не поддерживает REPEATABLE_READ .
4.5. СЕРИАЛИЗИРУЕМАЯ изоляция
СЕРИАЛИЗИРУЕМЫЕ является наивысшим уровнем изоляции. Это предотвращает все упомянутые побочные эффекты concurrency, но может привести к самой низкой скорости одновременного доступа, поскольку он выполняет параллельные вызовы последовательно.
Другими словами, одновременное выполнение группы серийных транзакций имеет тот же результат, что и их выполнение в последовательном.
Теперь давайте посмотрим, как установить СЕРИАЛИЗИРУЕМЫЕ в качестве изоляция уровень:
5. Заключение
В этом учебнике мы исследовали распространение собственности @Transaction обстоятельно. После этого мы узнали о побочных эффектах с эквивалентности и уровнях изоляции.
Изоляция и распространение транзакций в Spring
В статье, посвящённой декларативному управлению транзакциями в Spring я обещал отдельно описать изоляцию транзакций друг от друга и их распространение. Это время пришло.
Изоляция транзакций
Я уже писал про побочные эффекты, вызываемые параллельным исполнением запросов. Уровни изоляции транзакций можно рассматривать как механизм, позволяющий решать проблему параллельного доступа к данным и изменения данных без явных ручных блокировок.
Существует четыре уровня изоляции транзакций, в которых подобные побочные эффекты могут происходить, а могут и не происходить:
Распространение транзакций
Когда вызывается метод с @Transactional происходит особая уличная магия: proxy, который создал Spring, создаёт persistence context (или соединение с базой), открывает в нём транзакцию и сохраняет всё это в контексте нити исполнения (натурально, в ThreadLocal ). По мере надобности всё сохранённое достаётся и внедряется в бины. Привязка транзакций к нитям (threads) позволяет использовать семантику серверов приложений J2EE, в которой гарантируется, что каждый запрос получает свою собственную нить.
Все эти правила действуют как при вызове метода в текущем потоке, так и выполнения в другом потоке. В случае другого потока транзакция будет относится к нему.
Куда же ставить @Transactional?
Классическое приложение обычно имеет многослойную архитектуру:
контроллеры > слой логики > слой доступа к данным > слой ORM
Слой логики представляется идеальным местом для @Transactional : именно здесь набор запросов к базе оформляется в единую осмысленную операцию в приложении. Зная, что делает ваше приложение, вы можете чётко разграничить логические единицы работы в нём и расставить границы транзакций.