Документація розробника Hibernate – Глава II. Транзакції і контроль багатопоточності

Представляю вашій увазі переклад другий глави офіційній документації Hibernate.

Переклад статті актуальний для версії Hibernate 4.2.19.Final

Зміст

 2.1. Визначення транзакції
 2.2. Фізичні транзакції
   2.2.1. Фізичні транзакції — JDBC
   2.2.2. Фізичні транзакції — JTA
   2.2.3. Фізичні транзакції — CMT
   2.2.4. Фізичні транзакції — Інше
   2.2.5. Фізичні транзакції — Застаріле
 2.3. Застосування транзакцій Hibernate
 2.4. Патерни і анти-патерни транзакцій
   2.4.1. Анти-патерн сесія-на-операцію
   2.4.2. Патерн сесія-на-запит
   2.4.3. Діалоги (Conversations)
 2.5. Ідентичність об'єкта
 2.6. Загальні питання

2.1. Визначення транзакції
Важливо розуміти, що термін «трансакція» має багато сенсів щодо персистентності і об'єктно-реляційного проектування.
У більшості, але не у всіх випадках, підходять наступні визначення.
  • Може мати відношення до фізичних транзакцій БД.
  • Може мати відношення до логічного поняття «трансакція», як пов'язаній з контекстом персистентності
  • Може відсилати нас до поняття Unit-of-Work, як цілком певному архітектурному шаблоном.
ВажливоВ даній документації розглядається логічне та фізичне поняття транзакції як одне поняття


2.2. Фізичні транзакції
Hibernate використовує JDBC API для персистентності. У світі Java є два добре визначених механізму роботи з транзакціями: безпосередньо JDBC і JTA. Hibernate підтримує обидва механізму інтеграції з транзакціями і дозволяє додаткам керувати фізичними транзакціями.

Перша концепція у розумінні підтримки транзакцій в Hibernate – це інтерфейс org.hibernate.engine.transaction.spi.TransactionFactory, який надає дві основні функції:
  • Він дозволяє Hibernate розуміти семантику транзакцій поточного оточення. Чи ми працюємо зараз в оточенні JTA? Є фізична транзакція в даній момент вже активною, і. т. д
  • Він виступає як фабрика примірників org.hibernate.Transaction, використовуваних додатком для управління і перевірки стану транзакцій, org.hibernate.Transaction – поняття логічної транзакції в Hibernate's. JPA має схожу поняття в інтерфейсі javax.persistence.EntityTransaction.
Важливоjavax.persistence.EntityTransaction доступний тільки тоді, коли ви використовуєте resource-local транзакції. Hibernate надає доступ до org.hibernate.Transaction в незалежності від оточення.

org.hibernate.engine.transaction.spi.TransactionFactory – стандартний сервіс Hibernate. См. Подробиці в секції 7.5.16, “org.hibernate.engine.transaction.spi.TransactionFactory".

2.2.1. Фізичні транзакції — JDBC
Управління транзакціями за допомогою JDBC досягається методамиjava.sql.Connection.commit() java.sql.Connection.rollback() (JDBC не визначає явного методу для ініціювання транзакції). В Hibernate, даний підхід представлений класом org.hibernate.engine.transaction.internal.jdbc.JdbcTransactionFactory

2.2.2. Фізичні транзакції — JTA
JTA-підхід до транзакцій досягається інтерфейсом javax.transaction.UserTransaction, одержуваним з API org.hibernate.service.jta.platform.spi.JtaPlatform. Цей підхід представлений класом org.hibernate.engine.transaction.internal.jta.JtaTransactionFactory
См. по інтеграції з JTA Секція 7.5.9,“org.hibernate.service.jta.platform.spi.JtaPlatform"

2.2.3. Фізичні транзакції — CMT
Інший JTA-орієнтований підхід до транзакцій використовує інтерфейсjavax.transaction.TransactionManager, одержуваний з API org.hibernate.service.jta.platform.spi.JtaPlatform. Цей підхід представлений класом org.hibernate.engine.transaction.internal.jta.CMTTransactionFactory. В актуальному оточенні JEE CM, доступ до javax.transaction.UserTransaction закритий.
ВажливоТермін CMT потенційно може ввести в оману. Важлива частина полягає в тому, що фізичні JTA-транзакції керуються якимось іншим засобом, відмінним від API Hibernate.

См. по інтеграції з JTA Секція 7.5.9,“org.hibernate.service.jta.platform.spi.JtaPlatform".

2.2.4. Фізичні транзакції — Інше
Також можливо підключити користувальницький рішення з управління транзакціями, реалізувавши контракт org.hibernate.engine.transaction.spi.TransactionFactory. Ініціатор служби за замовчуванням має вбудовану підтримку розпізнавання користувача рішень через hibernate.transaction.factory_class, що може вказувати на:
  • Примірник org.hibernate.engine.transaction.spi.TransactionFactory.
  • Ім'я класу-реалізації org.hibernate.engine.transaction.spi.TransactionFactory. Клас-реалізація повинен мати конструктор без аргументів.


2.2.5. Фізичні транзакції — Застаріле
Більшість тих класів, названих вище, були перенесені в нові пакети під час розробки версії 4.0. Для допомоги при переході на нову версію, Hibernate буде розпізнавати застарілі імена на нетривалий період часу.
  • org.hibernate.transaction.JDBCTransactionFactory маппится на org.hibernate.engine.transaction.internal.jdbc.JdbcTransactionFactory
  • org.hibernate.transaction.JTATransactionFactory маппится на org.hibernate.engine.transaction.internal.jta.JtaTransactionFactory
  • org.hibernate.transaction.CMTTransactionFactory маппится на org.hibernate.engine.transaction.internal.jta.CMTTransactionFactory


2.3. Застосування транзакцій Hibernate
Hibernate використовує з'єднання JDBC і ресурси JTA безпосередньо, без додаткової логіки синхронізації. Для вас важливо ознайомитися з JDBC, ANSI SQL, і специфікою ізоляції транзакцій у вашій СУБД.
Hibernate не проводить синхронізацію на об'єктах в пам'яті. Поведінка, певне рівнем ізоляції ваших БД-транзакцій не змінюється, коли ви використовуєте Hibernate. Об'єкт org.hibernate.Session виступає як надає повторювані читання (repeatable reads) і запити кеш, обмежений межами транзакції.

ВажливоДля зменшення конкуренції за блокування, транзакції повинні бути як можна більш короткими. Довгі транзакції ускладнюють масштабування вашого додатка до високих навантажень. Не треба тримати транзакції відкритими під час роботи кінцевого користувача, їх потрібно відкривати після завершення роботи користувача. Ця концепція також по-іншому називається транзакційний write-behind.


2.4. Патерни і анти-патерни транзакцій
2.4.1. Анти-патерн сесія-на-операцію
Це анти-патерн про відкриття та закриття об'єкта Session на кожну операцію до БД в одному потоці. Це також анти-патерн у термінах транзакцій БД. Групуйте ваші дзвінки в одну заплановану послідовність. Також, не робіть авто-комміт транзакції на кожне SQL-вираз. Hibernate вимикає, або чекає, що сервер додатків негайно вимкне режим авто-коміта. Транзакції до БД ніколи не були чимось необов'язковим. Всі комунікації з БД повинні бути обгорнуті в транзакцію. Уникайте авто-коміта при читанні даних, тому як досить рідко безліч невеликих транзакцій будуть працювати швидше, ніж одна належним чином визначена транзакція. До того ж, таке безліч транзакцій важко підтримувати і розширювати.
ВажливоВикористання автокоммита не обов'язково призводить до використання БД-транзакцій на кожен вираз. Замість цього, в режимі автокоммита драйвера JDBC просто проводять кожен виклик в рамках неявного виклику транзакції. Це теж саме, як якщо б ваш додаток проводило виклик commit() транзакції після кожного виклику JDBC.

2.4.2. Патерн сесія-на-запит
Найбільш поширений патерн транзакцій. Термін «запит» тут слід розуміти в контексті системи, що реагує на серії запитів від користувача/клієнта. Веб-додатки є основним прикладом таких систем, але, звичайно, не тільки вони одні. На етапі початку обробки запиту, додаток відкриває об'єкт Session, ініціює трансакцію, проводить всю супутню роботу з даними, завершує транзакцію і закриває Session. Суть шаблону – це відношення один-до-одного між трансакцією і сесією.
В рамках патерну є поширена техніка визначення поточної сесії для спрощення передачі цієї Session між компонентами програми. Hibernate надає підтримку даної техніки через метод getCurrentSession() класу SessionFactory. Концепція «поточної сесії повинна мати область видимості, яка визначає межі, в яких визначення „поточна“ вірно. Це завдання контракту org.hibernate.context.spi.CurrentSessionContext. Є дві надійно визначених області видимості:
  • JTA транзакція, яка через callback може дати знати Hibernate, коли вона завершилася, що в свою чергу надає можливість завершити поточну сесію. Дана стратегія представлена org.hibernate.context.internal.JTASessionContext – реалізацією контракту org.hibernate.context.spi.CurrentSessionContext. З використанням цієї реалізації, Session буде відкритий, як тільки викличетьсяgetCurrentSession() в межах транзакції.
  • Цикл запиту сам по собі. Краще всього представлено org.hibernate.context.internal.ManagedSessionContext — реалізацією контракту org.hibernate.context.spi.CurrentSessionContext. Тут є зовнішній компонент, відповідальний за управління життєвим циклом і областю видимості „поточної сесії. На етапі старту області видимості, метод bind() викликається у ManagedSessionContext з передачею посилання на сесію. В кінці, викликається метод unbind().

ВажливоМетод getCurrentSession() має одну неприємну сторону в JTA. Якщо ви використовуєте його, after_statement режим звільнення з'єднань також буде використовуватися за замовчуванням. З-за обмежень JTA, Hibernate не може автоматично очищати будь незакритий примірник ScrollableResults або Iterator, що повертаються scroll() або iterate(). Звільнення курсорів БД здійснюється викликом ScrollableResults.close() або Hibernate.close(Iterator) явно з секції finally.

2.4.3. Діалоги
Патерн сесія-на-запит не є єдиним засобом дизайну units of work. Безліч бізнес-процесів вимагають всієї серії взаємодій з користувачем, які чергуються з доступом до БД. У веб — і ентерпрайз — додатках, не прийнятно для транзакції БД охоплювати весь контент взаємодія. Розглянемо наступний приклад:

Процедура 2.1. Приклад “довгограючого» діалогу
  1. Відкривається перший екран діалогу. Дані показуються користувачеві, підвантажуються в окремій сесії Session БД-транзакції. Користувач може модифікувати будь-які поля діалогу.
  2. Після п'яти хвилин редагування, користувач використовує UI елемент для збереження. Зміни відбилися в БД. Користувач також очікує ексклюзивного доступу до даних на час сесії редагування


Навіть, хоча ми і маємо кілька випадків доступу до БД, з точки зору користувача, дана серія кроків представляє одну одиниці досконалої роботи (Unit of Work). Є безліч шляхів реалізації цього в додатку.

Перший (наївний) метод полягає в утриманні відкритими сесій Session і транзакції на час редагування користувача, з використанням механізмів синхронізації БД для забезпечення ексклюзивного доступу користувачів до редагування даних і запобігання звернення до них з боку інших користувачів, гарантуючи ізоляцію і атомарність. Це анти-патерн, так як зайва синхронізація є вузьким місцем при проблемах продуктивності, що постають у високонавантажених додатках.

Ряд транзакцій БД використовується для реалізації діалогу з БД. В даному випадку, забезпечення ізоляції бізнес-процесів лягає на плечі програми. Один діалог зазвичай покриває кілька транзакцій. Множинні доступи до БД можуть бути атомарними, якщо тільки одна транзакція (звичайно остання) здійснює запис у БД. Всі інші тільки читають дані. Типовий шлях реалізації – через створення wizard-style діалогу, що покриває кілька кроків циклу запит/відповідь. Hibernate включає в себе деякі можливості, що дозволяють реалізувати подібний функціонал.

  • Автоматичне версіонування
    Hibernate може здійснювати за вас concurrency-контроль. Він може автоматично виявити, чи здійснювались сторонні
    оновлення даних за час очікування користувача.
  • Від'єднані (Detached) об'єкти
    Якщо ви віддасте перевагу використовувати шаблон сесія-на-запит,
    всі завантажені екземпляри будуть від'єднані за час очікування користувача. Hibernate дозволяє вам назад під'єднати
    об'єкти і зберегти модифікації. Даний патерн називається сесія-на-запит-з-отсоединенными об'єктами. Автоматичне версіонування використовується для ізоляції паралельно виконуються запитів.
  • Розширена сесія
    Сесія Session може бути від'єднана від нижчого JDBC з'єднання після того, як БД транзакція буде закоммичена, і переподсоединена, коли виникне новий клієнтський запит. Цей патерн називається сесія-на-діалог, робить повторне з'єднання (reattachment) об'єктів непотрібним. Автоматичне версіонування використовується для ізоляції паралельних модифікацій, при цьому сесія не може бути скинута (flushed) автоматично, тільки явно.
Сесія-на-запит з від'єднаним-об'єктами сесія-на-діалор мають свої плюси і мінуси.

2.5. Ідентичність об'єкта
Додаток може здійснювати паралельний доступ до одного і того ж persistent-станом (рядок у базі даних) в двох різних сесіях. Однак, примірник persistent-класу ніколи не розділяється між двома різними сесіями. Мають місце бути і вступають в гру два різних поняття ідентичності: БД-ідентичність і JVM-ідентичність.

Приклад 2.1. БД-ідентичність
foo.getId().equals( bar.getId() )


Приклад 2.2. JVM-ідентичність
foo==bar


Для об'єктів, приєднаних до однієї і тієї ж сесії Session, два поняття ідентичності еквівалентні, і JVM-ідентичність гарантується БД-ідентичність Hibernate'ом. Додаток може паралельно здійснювати доступ до бізнес-об'єкта з однієї і тієї ж БД-ідентичністю в двох різних сесіях, тим часом він буде представлений двома різними екземплярами Java-об'єктів, в термінах JVM-ідентичності. Вирішення конфліктів здійснюється оптимістичною стратегією і автоматичним версионированием під час скидання(flush)/коміта.

Цей підхід покладає відповідальність за управлінням паралельністю на Hibernate і БД. Він також забезпечує кращу масштабованість, так як дорогі блокування не потрібні для гарантії ідентичності в однопоточній одиниці роботи (single-threaded unit of work). Додатком немає потреби синхронізуватися на якому б то ні було бізнес-об'єкт, поки він працює в одному потоці. Хоч це і не рекомендується в межах сесії Session додаток може безпечно використовувати оператор == для порівняння об'єктів.

Однак, додаток, який використовує оператор == за межами сесії Session може внести деякі проблеми. Якщо ви додасте два від'єднаних примірника об'єкта в один Set, вони можливо будуть мати одну БД-ідентичність, тобто вони представляють одну й ту ж рядок у таблиці. Зовсім не гарантовано, що вони будуть мати одну і ту ж JVM-ідентичність, будучи в стані detached. Перевизначити методи equals() і hashCode() в persistent-класах, так що вони будуть мати власне визначення об'єктної еквівалентності. Не використовуйте БД-ідентичність для реалізації перевірки на рівність. Замість цього, використовуйте бізнес-ключ, який є комбінацією унікальних, незмінних атрибутів. БД ідентифікатор може змінитися, якщо об'єкт перейде зі стану transient в стан persistent. Якщо transient примірник зберігається разом з detached примірником в одному Set'e – зміна хэшкода порушить контракт Set'а. Атрибути для бізнес-ключа можуть бути менш стійкі ніж основні ключі. Вам тільки необхідно гарантувати стабільність до тих пір, поки об'єкти знаходяться в одному Set'тобто Це не проблема Hibernate, так як ставиться до реалізації об'єктної ідентичності та еквівалентності в Java.

2.6. Загальні питання
Обидва анти-патерну сесія-на-сесію-користувача сесія-на-додаток сприйнятливі до наступних проблем. Частина з цих проблем може виникнути також і в рекомендованих шаблонах, так що для початку переконайтеся, що ви розумієте наслідки, перед тим як приймати якісь рішення по дизайну:
  • Сесія Session не є потокобезопасной. Сутності, що працюють паралельно, такі як HTTP-запити, session-бины, або Swing worker'и, призведуть до виникнення ситуацій гонки (race conditions) якщо сесія Session ділиться між потоками. Якщо ви зберігаєте вашу сесію Hibernate у вашій сесіїjavax.servlet.http.HttpSession (буде обговорено пізніше), вам потрібно розглянути проблему синхронізованого доступу до вашої HttpSession; інакше, користувач, кликающий кнопку 'Оновити' занадто швидко, буде використовувати одну і ту ж сесію в двох паралельно виконуваних потоках.
  • Виняток, що викидається Hibernate'ом означає, що ви повинні відкотити(rollback) вашу транзакцію і закрити сесію Session негайно (обговорюється більш детально в наступних розділах). Якщо ваша сесія обмежена додатком, ви повинні зупинити додаток. Відкат транзакції не відкочує ваші бізнес-об'єкти до стану, в якому вони перебували на момент початку транзакції. Це означає, що стан в БД і стан об'єктів піддалося розсинхронізації. Звичайно, це не проблема т. до винятку не відновити, і вам все одно потрібно буде починати спочатку після відкату.
  • Сесія кешує кожен об'єкт, що знаходиться в стані persistent (тобто він моніториться і перевіряється на зміни Hibernate'ом). Якщо ви залишите її на довготривалий період, або просто завантажте занадто багато даних, вона зросте багаторазово, до тих пір, поки ви не отримаєте OutOfMemoryException. Є рішення викликати clear() evict() для керування кешем сесії Session, але вам слід розглянути альтернативні способи роботи з великою кількістю даних, такі як збережені процедури. Java не є підходящим інструментом для подібного роду операцій. Деякі рішення показані в Главі 4, Пакетна обробка. Сесія, що залишається відкритою на період роботи сесії користувача також означає високу ймовірність появи «несвіжих».


Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.