1. Знайомство з транзакціями в Hibernate

До всього, що ми вже розібрали, хотілося б ще додати інформацію про транзакції. Як ти вже знаєш, транзакція — це група дій, які мають бути виконані лише всі разом. Якщо будь-яка дія не виконалася або виконалася з помилкою, всі інші дії повинні бути скасовані.

Hibernate вміє працювати з двома видами транзакцій:

  • JDBC
  • JTA

JDBC-транзакція — це фактично транзакція бази даних. Вона прив'язана до роботи з базою даних, JDBC-з'єднання. І слідкує за тим, щоб дії під час роботи з базою даних виконувались як слід: або всі чи нічого.

JTA-транзакція — це транзакція рівня програми. Вона не прив'язана до жодної бази даних. Її завдання — слідкувати, щоб деякі дії виконувались: всі, або жодна.

Наприклад, ти можеш записувати дані до кількох різних баз даних у межах однієї JTA-транзакції. Тоді, якщо відбудеться помилка, JTA-транзакція повинна буде відкотити зміни у всіх базах даних. Навіть ті, які були успішно виконані з точки зору конкретної бази даних.

2. Hibernate Transactions Interface

У бібліотеці Hibernate транзакція представлена інтерфейсом Transaction, який може мати різні реалізації. Наприклад, під час роботи зі Spring, Spring надає свій власний механізм JTA-транзакцій.

Методи цього інтерфейсу такі:

# Метод Опис
1 begin() Стартує нову транзакцію
2 commit() Закінчує транзакцію, пушить/комітить всі зміни
3 rollback() Відточує поточну транзакцію
4 setTimeout(int seconds) Встановлює максимальний час виконання транзакції
5 isActive() Перевіряє, активна транзакція чи ні
6 wasRolledBack() Перевіряє, чи нормально відкотилася транзакція
7 wasCommitted() Перевіряє, чи транзакція нормально закомітилася
8 registerSynchronization() Реєструє callback для контролю транзакції

Важливо! Створення об'єкта транзакції та запуск транзакції — це різні речі. Тут можна провести аналогію з класом Thread. Коли ти створюєш об'єкт Thread(), новий потік JVM ще не запускає. Щоб його запустити, потрібно викликати метод start() об'єкта Thread. Те саме і з транзакцією: їй потрібно викликати метод begin().

Приклад того, як зазвичай працюють з транзакціями до Hibernate:


Session session = sessionFactory.openSession();
Transaction transaction = session.getTransaction();
try {
    transaction.begin();
    Long count = session.createQuery("select count(*) from Employee", Long.class). uniqueResult();
    transaction.commit();
}
catch (Exception e) {
if (transaction.getStatus() == ACTIVE ||  transaction.getStatus() == MARKED_ROLLBACK) {
    transaction.rollback();
    }
}
finally {
session.close();
sessionFactory.close();
}

Ми бачимо тут три речі:

По-перше, вся робота з базою обертається в транзакцію за допомогою виклику методів begin() та commit() Усі дії між викликами цих двох методів мають бути виконані: або всі разом, або жодна.

По-друге, якщо сталася будь-яка помилка, ми намагаємося відкотити транзакцію — викликати метод rollback(). Це означає, що TransactionManger повинен спочатку записувати всі дії, які були між begin() і commit(), а потім повернути все, як було, якщо ми викликали rollback ().

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

3. Transaction Manager

З точки зору менеджменту транзакцій, Hibernate — це просто полегшена оболонка об'єктів для JDBC. Сам Hibernate немає функцій обробки транзакцій. Hibernate Transaction є оболонкою для базової транзакції JDBC (або оболонки транзакцій JTA). JDBCTransaction використовується за замовчуванням. Приклад із файлу налаштувань Hibernate:


hibernate.transaction.factory_class org.hibernate.transaction.JTATransactionFactory
hibernate.transaction.factory_class org.hibernate.transaction.JDBCTransactionFactory

Давай ще раз подивимося на наш код із використанням транзакцій:


Session session = sessionFactory.openSession();
Transaction transaction = session.getTransaction();
transaction.begin();
//тут ваш код роботи з базою
session.flush();
transaction.commit();
session.close();

А тепер давай подивимося на код класу JDBCTransaction:


public class JDBCTransaction implements Transaction {
 
    public void begin() throws HibernateException {
    ...
    if (toggleAutoCommit) jdbcContext.connection().setAutoCommit(false);
    ...
    }
}

Це метод для запуску транзакції. Потім подивитися на метод відправлення:


public void commit() throws HibernateException {
    ...
    jdbcContext.connection().commit( );
    ...
    jdbcContext.connection().setAutoCommit( true );
    ...
}

Тепер давай підставимо цей код у код в прикладі з Hibernate:

Hibernate Простий JDBC-код

Session session = sessionFactory.openSession();
Transaction transaction = session.getTransaction();
transaction.begin();
//тут твій код для роботи з базою
session.flush();
transaction.commit();
session.close();

Connection conn = jdbcContext.connection() ;
conn.setAutoCommit(false);
 
//Тут твій код для роботи з базою
conn.commit ()
conn.setAutoCommit(true);
conn.close();

Отже рідні транзакції Hibernate — це просто рідні JDBC-дзвінки до бази даних. Не більше і не менше. А ось JTA транзакції — це вже цікавіше. Але про це іншим разом.