Як насправді працює @transactional spring блог Анатолія Корсакова

У цій статті ми спускаємося в нетрі менеджменту транзакцій фреймворка Spring. Ми розглянемо як влаштована під «капотом» робота анотації @Transactional.

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

Важливо відзначити, що специфікація JPA сама по собі не надає ніякого декларативного управління транзакціями. При використанні JPA поза DI (Dependency Injection) контейнера, транзакції управляються програмно самим розробником:

Такий спосіб контролю за транзакціями надає прозорості коду, але є кілька недоліків:

  • Багато повторюваного коду і схильність до помилкового поведінки
  • Будь-яка помилка має дуже великий вплив
  • Помилки важко налагодити і відтворити
  • Ускладнює читабельність коду
  • Що робити якщо цей методу викликає інший метод з транзакціями?

Використовуємо анотацію Spring @Transactional

C анотацією @Transactional, приклад можна спростити наступним чином:

Такий код набагато зручний і удобочитаем, і на даний момент рекомендується використовувати такий підхід для управління транзакціями в Spring.

Використовуючи @Transactional, багато важливих аспектів як розповсюджуваність транзакцій (propagation) обробляються автоматично. в цьому випадку, якщо інший метод із транзакцією зголоситься методом businessLogic (), цей метод приєднається до виконуваної транзакції.

Один недолік все таки є: складно зрозуміти як працює зсередини цей потужний механізм і складно налагоджувати при виникненні помилки.

Що значить @Transactional?

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

  • persistence контекст
  • транзакція БД

Анотація сама по собі визначає область дії однієї транзакції БД. Транзакція БД відбувається всередині області дій persistence context.

Persistence контекстом в JPA є EntityManager, який використовує всередині клас Session ORM-фреймворка Hibernate (при використанні Hibernate як persistence провайдера).

Persistence контекст це об'єкт-сінхронайзер, який відстежує стану обмеженого набору Java об'єктів і синхронізує зміни станів цих об'єктів зі станом відповідних записів в БД.

Один об'єкт Entity Manager не завжди відповідає одній транзакції БД. Один об'єкт Entity Manager може бути використаний декількома транзакціями БД.

Коли EntityManager охоплює безліч транзакцій БД?

Найчастіший випадок відбувається коли додаток використовує шаблон «Open Session in View» для запобігання винятків «ледачою» ініціалізації.

У цьому випадку запити, які могли бути виконані однією транзакцією при виклику з шару сервісу, виконуються в окремих транзакціях в шарі View, але вони відбуваються через один і той же Entity Manager.

Інший випадок це коли persistence context відзначений розробником як PersistenceContextType.EXTENDED, що означає що в додатку використовується антипаттерн «одна сесія на додаток».

Що визначає відношення між EntityManager і транзакцією?

Звичайно кожен розробник вирішує сам, але найчастіший вибір це використовувати JPA Entity Manager зі стратегією «один Entity Manager на одну транзакцію додатки». Наступний код демонструє, як можна впровадити Entity Manager в Bean:

У цьому прикладі за замовчуванням працює режим «один Entity Manager на одну транзакцію додатки». У цьому режимі, якщо ми використовуємо Entity Manager всередині методу поміченого анотацією @Transactional, то цей метод буде виконуватися в єдиній транзакції БД.

Як працює @PersistenceContext?

Одне питання приходить в голову, як може @PersistenceContext впровадити entity manager один раз при старті контейнера, враховуючи що час життя entity менеджерів дуже мало, і зазвичай їх потрібно кілька на один запит?

Відповідь така: не може. Entity Manager це інтерфейс, і то що впроваджується в бін не є самим по собі entity менеджером, це context aware proxy, який буде делегувати до конкретного entity менеджеру в Рантайм.

Зазвичай цей конкретний клас використовується для проксі це SharedEntityManagerInvocationHandler, в чому можна переконатися за допомогою дебаггера.

Як тоді працює @Transactional?

Проксі persistence контексту, яке імлементірует EntityManager не є достатнім набором компонентів для здійснення декларативного управління транзакціями. Насправді потрібно три компоненти:

  • Проксі Entity менеджера
  • аспект транзакцій
  • Менеджер транзакцій

Давайте розглянемо кожен і подивимося їх взаємодія.

аспект транзакцій

Аспект транзакцій - «around» аспект, який викликається і до і після виконання анотованого бізнес методу. Конкретний клас для імплементації цього аспекту це TransactionInterceptor.

Аспект транзакцій має дві головні функції:

  • У момент «до» аспект визначає виконати чи виконується метод в межах вже сущестувующей транзакції БД або повинна стартувати нова окрема транзакція.
  • У момент «після» аспект вирішує що робити з транзакцією, робити Комміт, відкат або залишити незакритих.

У момент «до» аспект сам не містить логіки щодо прийняття рішення, рішення почати нову транзакцію, якщо це потрібно, делегується Transaction менеджеру.

Transaction менеджер

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

  • Чи повинен створитися новий Entity Manager?
  • Чи повинна стартувати нова транзакція БД?

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

  • чи виконується хоч одна транзакція в поточний момент чи ні
  • атрибута «propagation» у методу, анотованого @Transactional (для прикладу, REQUIRES_NEW завжди стартує нову транзакцію).

Якщо менеджер вирішив створити нову транзакцію, тоді:

  • Створюється новий entity менеджер
  • «Прив'язка» entity менеджера до поточного потоку (Thread)
  • «Взяття» з'єднання з пулу з'єднань БД
  • «Прив'язка» з'єднання до поточного потоку

І entity менеджер і це з'єднання прив'язуються до поточного потоку, використовуючи змінні ThreadLocal.

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

Будь-яка частина програми, яку потрібен поточний entity manager або з'єднання, може дістати їх з потоку. Цим компонентом програми, який робить саме так є Entity Manager Proxy.

EntityManager proxy

Проксі Entity менеджера (який був представлений раніше) це останній кусоче пазла. Коли бізнес метод робить виклик, наприклад, entityManager.persist (). цей виклик не викликається безпосередньо у entity менеджера.

Замість цього бізнес метод викликає проксі, який дістає поточний entity менеджер з потоку, в який його поклав менеджер транзакцій.

Знаючи тепер всі частини механізму @Transactional, давайте пройдемося по звичайній конфігурації Spring, необхідної для роботи всього цього.

Складемо весь паззл разом

Давайте опишемо конфігурацію трьох компонентів, необхідних для належного функціонування анотації @Transactional. Почнемо з визначення Entity Manager Factory.

Це дозволить инжектировать проксі Entity менеджера через анотацію @PersistenceContext:

Слудующим кроком сконфігуріруем Менеджер Транзакцій і застосуємо Аспект транзакцій до класів анотований @Transactional:

Анотація @EnableTransactionManagement вказує Спрінг, що класи з анотацією @Transactional, повинні бути обгорнуті аспектом транзакцій. Тепер можна використовувати анотацію @Transactional.

висновок

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

Розуміння як він працює дуже корисно при вирішенні виникаючих проблем, коли механізм працює не так як очікується.

Найголовніша річ, яку потрібно завжди тримати в голові, це те що є дві концепції, які потрібно брати до уваги: ​​транзакція БД і persistence контекст, кожна зі своїм власним неочевидним життєвим циклом.

(Visited 3 457 times, 28 visits today)

Поділитися посиланням: