Как Hibernate управляет транзакциями базы данных и как он интегрируется с @Transactional Spring?
Spring регистрирует JpaTransactionManager, а @Transactional через AOP-прокси автоматически открывает/коммитит/откатывает Hibernate-транзакцию; dirty checking позволяет не вызывать save() явно — изменения флашатся при коммите.
Транзакции в Hibernate и интеграция со Spring @Transactional
Как Hibernate управляет транзакциями
Hibernate сам по себе не создаёт транзакции автоматически — он делегирует управление через абстракцию org.hibernate.Transaction (нативный API) или JPA EntityTransaction. При работе через JTA (Jakarta Transactions) транзакции координируются контейнером или менеджером транзакций (Atomikos, Bitronix, Narayana).
В автономном режиме транзакция выглядит так:
Session session = sessionFactory.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
Employee emp = session.get(Employee.class, 1L);
emp.setSalary(100_000);
tx.commit();
} catch (Exception e) {
if (tx != null) tx.rollback();
throw e;
} finally {
session.close();
}
Роль Spring в управлении транзакциями
Spring предоставляет абстракцию PlatformTransactionManager. При использовании Hibernate + JPA Spring регистрирует JpaTransactionManager, который управляет жизненным циклом EntityManager и оборачивает его транзакцию в прокси-метод.
@Configuration
@EnableTransactionManagement
public class PersistenceConfig {
@Bean
public PlatformTransactionManager transactionManager(
EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
}
Аннотация @Transactional
Spring AOP перехватывает вызов метода, помеченного @Transactional, и выполняет:
- Открывает транзакцию (или присоединяется к существующей).
- Выполняет метод.
- При успехе —
commit; при непроверяемом исключении —rollback.
@Service
public class EmployeeService {
@Autowired
private EmployeeRepository repo;
@Transactional
public void promote(Long empId, String newTitle) {
Employee emp = repo.findById(empId)
.orElseThrow(() -> new EntityNotFoundException("emp " + empId));
emp.setTitle(newTitle); // dirty checking — UPDATE выполнится при commit
// явный save() не нужен
}
@Transactional(readOnly = true)
public List<Employee> findAll() {
return repo.findAll(); // Hibernate не флашит, оптимизирует L2-кэш
}
@Transactional(rollbackFor = CheckedException.class)
public void riskyOp() throws CheckedException {
// проверяемое исключение тоже откатит транзакцию
}
}
Уровни распространения (Propagation)
- REQUIRED (по умолчанию) — присоединяется к существующей; если нет — создаёт.
- REQUIRES_NEW — всегда новая транзакция, текущая приостанавливается.
- SUPPORTS — участвует если есть, без транзакции если нет.
- NOT_SUPPORTED — приостанавливает текущую, работает без транзакции.
- NEVER — бросает исключение если транзакция активна.
- MANDATORY — требует активную транзакцию, иначе исключение.
- NESTED — savepoint внутри текущей транзакции (только JDBC, не JTA).
Dirty Checking и flush
Hibernate отслеживает изменения управляемых сущностей (dirty checking). При коммите транзакции он автоматически вызывает flush(), генерируя UPDATE-запросы. Явный save() не нужен для managed-сущностей.
Подводные камни
- Self-invocation: вызов
@Transactional-метода из того же класса минует AOP-прокси — транзакция не начнётся. Выносите вызов в другой бин или используйтеApplicationContext.getBean(). - private методы: Spring AOP на основе CGLIB не может перехватить
private @Transactional— аннотация молча игнорируется. - Checked исключения не откатывают по умолчанию: только
RuntimeExceptionвызывает rollback; для checked нужно явноrollbackFor. - readOnly = true не запрещает запись: это подсказка для драйвера и JDBC-пула (например, направление на реплику), но Hibernate физически не блокирует INSERT.
- LazyInitializationException вне транзакции: если сессия закрыта до сериализации, LAZY-поля не проинициализированы — используйте DTO или
@Transactionalв контроллере (осторожно — антипаттерн). - REQUIRES_NEW и lock: новая транзакция может заблокировать ту же строку, которую держит приостановленная — deadlock.
- Уровень изоляции: по умолчанию используется уровень изоляции СУБД; при необходимости —
@Transactional(isolation = Isolation.REPEATABLE_READ), но не все БД поддерживают все уровни через JDBC.
Common mistakes
- Путать термин «hibernate transactions spring» с соседним механизмом Hibernate.
- Не называть границу lifecycle, transaction, thread или request для «hibernate transactions spring».
- Игнорировать production-эффекты «hibernate transactions spring»: latency, SQL shape, memory, security или observability.
What the interviewer is testing
- Попросить объяснить механизм «hibernate transactions spring» на минимальном примере.
- Проверить, видит ли кандидат failure mode и диагностику для «hibernate transactions spring».
- Уточнить, какие настройки или API меняют «hibernate transactions spring» в реальном сервисе.