4.1 Знакомство с транзакциями в Hibernate

Ко всему вышесказанному хотелось бы еще добавить информацию про транзакции. Как ты уже знаешь, транзакция – это группа действий, которые должны быть выполнены только все вместе. Если какое-либо действие не выполнилось или выполнилось с ошибкой, то все остальные действия должны быть отменены.

Hibernate умеет работать с двумя видами транзакций:

  • JDBC
  • JTA

JDBC-транзакция, – это фактически транзакция базы данных. Она привязана к работе с базой данных, к JDBC-соединению. И следит за тем, чтобы действия при работе с базой данных выполнялись как надо: или все или ничего.

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

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

4.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 не произойдет ошибки. Ошибки всегда происходят. Тебе нужно просто принять этот факт и быть к нему готовым.

4.3 Transaction Manager

С точки зрения менеджмента транзакций, Hibernate – это просто облегченная оболочка объектов для JDBC. Сам Hibernate не имеет функций обработки транзакций. Hibernate Transaction на самом деле является оболочкой для базовой транзакции JDBC (или оболочки транзакций JTA). JDBCTransaction используется по умолчанию. Пример из файла настроек HIberante:

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 транзакции – это уже поинтереснее. Но об этом в другой раз.