HibernateSeniorTechnical

Как 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, и выполняет:

  1. Открывает транзакцию (или присоединяется к существующей).
  2. Выполняет метод.
  3. При успехе — 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» в реальном сервисе.

Sources

Related topics