Статьи

Как Spring @Transactional действительно работает?

В этом посте мы углубимся в управление транзакциями Spring. Мы рассмотрим, как @Transactional действительно работает под капотом. Другие предстоящие сообщения будут включать в себя:

  • как использовать такие функции, как распространение и изоляция
  • Каковы основные подводные камни и как их избежать

JPA и управление транзакциями

Важно отметить, что JPA сама по себе не обеспечивает какого-либо декларативного управления транзакциями. При использовании JPA вне контейнера внедрения зависимостей, транзакции должны обрабатываться разработчиком программно:

01
02
03
04
05
06
07
08
09
10
11
12
UserTransaction utx = entityManager.getTransaction();
 
    try {
        utx.begin();
 
        businessLogic();
 
        utx.commit();
    } catch(Exception ex) {
        utx.rollback();
        throw ex;
    }

Этот способ управления транзакциями делает область транзакции очень ясной в коде, но имеет несколько недостатков:

  • это повторяющееся и подверженное ошибкам
  • любая ошибка может иметь очень большое влияние
  • ошибки трудно отлаживать и воспроизводить
  • это уменьшает читабельность кода
  • Что если этот метод вызывает другой транзакционный метод?

Использование Spring @Transactional

В Spring @Transactional приведенный выше код сводится к следующему:

1
2
3
4
@Transactional
    public void businessLogic() {
        ... use entity manager inside a transaction ...
    }

Это намного удобнее и удобочитаемее, и в настоящее время это рекомендуемый способ обработки транзакций в Spring.

Используя @Transactional , многие важные аспекты, такие как распространение транзакций, обрабатываются автоматически. В этом случае, если businessLogic() вызывает другой транзакционный метод, этот метод будет иметь возможность присоединиться к текущей транзакции.

Одним из потенциальных недостатков является то, что этот мощный механизм скрывает то, что происходит под капотом, затрудняя отладку, когда что-то не работает.

Что означает @Transactional ?

Одним из ключевых моментов в @Transactional является то, что нужно рассмотреть две отдельные концепции, каждая со своей областью действия и жизненным циклом:

  • постоянный контекст
  • транзакция базы данных

Сама аннотация транзакции определяет область действия одной транзакции базы данных. Транзакция базы данных происходит внутри контекста персистентности .

Контекст персистентности находится в JPA EntityManager , реализованном внутренне с использованием Hibernate Session (при использовании Hibernate в качестве поставщика персистентности).

Контекст постоянства — это просто объект синхронизатора, который отслеживает состояние ограниченного набора объектов Java и гарантирует, что изменения в этих объектах в конечном итоге сохраняются обратно в базу данных.

Это совершенно другое понятие, чем понятие транзакции базы данных. Один Entity Manager может использоваться для нескольких транзакций базы данных , и это на самом деле часто.

Когда EntityManager охватывает несколько транзакций базы данных?

Наиболее частый случай, когда приложение использует шаблон Open Session In View для обработки отложенных исключений инициализации, см. Этот предыдущий пост в блоге, чтобы узнать его плюсы и минусы .

В таком случае запросы, которые выполняются на уровне представления, находятся в отдельных транзакциях базы данных, чем те, которые используются для бизнес-логики, но они выполняются через один и тот же менеджер сущностей.

Другой случай, когда контекст постоянства помечен разработчиком как PersistenceContextType.EXTENDED , что означает, что он может выдержать несколько запросов.

Что определяет отношение EntityManager против транзакции?

Это на самом деле выбор разработчика приложения, но наиболее распространенный способ использования JPA Entity Manager — это шаблон «Entity Manager для каждой транзакции приложения». Это наиболее распространенный способ внедрения менеджера сущностей:

1
2
@PersistenceContext
    private EntityManager em;

Здесь мы по умолчанию находимся в режиме «Entity Manager на транзакцию». В этом режиме, если мы используем этот Entity Manager внутри метода @Transactional , тогда метод будет выполняться в одной транзакции базы данных.

Как работает @PersistenceContext?

Один вопрос, который приходит на ум, заключается в следующем: как @PersistenceContext может внедрить диспетчер сущностей только один раз во время запуска контейнера, учитывая, что менеджеры сущностей настолько недолговечны и что их обычно многократно для каждого запроса.

Ответ заключается в том, что он не может: EntityManager — это интерфейс, и то, что внедряется в bean-компонент Spring, — это не сам менеджер сущностей, а прокси-сервер с учетом контекста , который делегирует конкретному менеджеру сущностей во время выполнения.

Обычно конкретным классом, используемым для прокси-сервера, является SharedEntityManagerInvocationHandler , это можно подтвердить с помощью отладчика.

Как тогда работает @Transactional?

Прокси-контекст постоянства, который реализует EntityManager — не единственный компонент, необходимый для работы декларативного управления транзакциями. На самом деле необходимы три отдельных компонента:

  • Сам EntityManager Proxy
  • Транзакционный аспект
  • Менеджер транзакций

Давайте пройдемся по каждому и посмотрим, как они взаимодействуют.

Транзакционный аспект

Транзакционный аспект — это аспект «вокруг», который вызывается как до, так и после аннотированного бизнес-метода. Конкретный класс для реализации аспекта — TransactionInterceptor .

Транзакционный аспект имеет две основные обязанности:

  • В момент «до» аспект обеспечивает точку подключения для определения того, должен ли бизнес-метод, который должен быть вызван, выполняться в области текущей транзакции базы данных или должна быть запущена новая отдельная транзакция.
  • В момент «после» аспект должен решить, должна ли транзакция быть зафиксирована, откатана или запущена.

В момент «до» сам Транзакционный аспект не содержит никакой логики принятия решения, решение начать новую транзакцию, если необходимо, делегируется Менеджеру транзакций.

Менеджер транзакций

Менеджер транзакций должен дать ответ на два вопроса:

  • должен быть создан новый Entity Manager?
  • должна быть запущена новая транзакция базы данных?

Это необходимо решить в тот момент, когда вызывается логика транзакционного аспекта «до». Менеджер транзакций примет решение на основании:

  • тот факт, что одна транзакция уже выполняется или нет
  • атрибут распространения транзакционного метода (например, REQUIRES_NEW всегда запускает новую транзакцию)

Если менеджер транзакций решит создать новую транзакцию, он будет:

  • создать новый менеджер сущностей
  • привязать менеджер сущностей к текущему потоку
  • захватить соединение из пула соединений с БД
  • привязать соединение к текущей теме

Диспетчер сущностей и соединение связаны с текущим потоком с помощью переменных ThreadLocal .

Они хранятся в потоке во время выполнения транзакции, и менеджер транзакций может очистить их, когда они больше не нужны.

Любые части программы, которым нужен текущий менеджер сущностей или соединение, могут извлечь их из потока. Одним из компонентов программы, который делает именно это, является прокси EntityManager.

EntityManager прокси

Прокси-сервер EntityManager (который мы представили ранее) — это последняя часть головоломки. Когда бизнес-метод вызывает, например,
entityManager.persist() , этот вызов не вызывает менеджера сущностей напрямую.

Вместо этого бизнес-метод вызывает прокси-сервер, который извлекает текущий менеджер сущностей из потока, куда его поместил менеджер транзакций.

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

Собираем все вместе

Давайте рассмотрим, как настроить три компонента, необходимые для правильной работы аннотации транзакции. Начнем с определения фабрики менеджера сущностей.

Это позволит внедрить прокси Entity Manager с помощью аннотации постоянного контекста:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
@Configuration
    public class EntityManagerFactoriesConfiguration {
        @Autowired
        private DataSource dataSource;
 
        @Bean(name = "entityManagerFactory")
        public LocalContainerEntityManagerFactoryBean emf() {
            LocalContainerEntityManagerFactoryBean emf = ...
            emf.setDataSource(dataSource);
            emf.setPackagesToScan(
                new String[] {"your.package"});
            emf.setJpaVendorAdapter(
                new HibernateJpaVendorAdapter());
            return emf;
        }
    }

Следующим шагом является настройка диспетчера транзакций и применение @Transactional транзакции в аннотированных классах @Transactional :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
@Configuration
    @EnableTransactionManagement
    public class TransactionManagersConfig {
        @Autowired
        EntityManagerFactory emf;
        @Autowired
        private DataSource dataSource;
 
        @Bean(name = "transactionManager")
        public PlatformTransactionManager transactionManager() {
            JpaTransactionManager tm =
                new JpaTransactionManager();
                tm.setEntityManagerFactory(emf);
                tm.setDataSource(dataSource);
            return tm;
        }
    }

Аннотация @EnableTransactionManagement сообщает Spring, что классы с аннотацией @Transactional должны быть обернуты аспектом транзакций. Теперь @Transactional готов к использованию.

Вывод

Механизм декларативного управления транзакциями Spring очень мощный, но его можно использовать неправильно или легко настроить.

Понимание того, как это работает внутри, полезно при устранении неполадок в ситуациях, когда механизм вообще не работает или работает неожиданным образом.

Самая важная вещь, которую нужно иметь в виду, — это то, что на самом деле нужно учитывать две концепции: транзакция базы данных и контекст постоянства, каждая из которых имеет свой неочевидный жизненный цикл.

В будущем сообщении будут рассмотрены наиболее частые ошибки в транзакционной аннотации и способы их устранения.

Ссылка: Как Spring @Transactional действительно работает? от нашего партнера JCG Алексея Новика в блоге The JHades Blog .