EntityManager и контекст персистентности
Вся работа с сущностями идёт через два объекта. EntityManagerFactory создаётся один раз на приложение: в нём настройки, сведения о классах и соединения с базой. EntityManager создаётся на одну единицу работы — например, на один веб-запрос — и после неё закрывается. Внутри каждого EntityManager живёт контекст персистентности (persistence context): набор объектов, которыми он сейчас управляет.
Транзакция вручную
Метод runInTransaction из прошлого урока — сокращение. Вот что он делает внутри:
// src/main/java/com/example/blog/Main.java
package com.example.blog;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;
public class Main {
public static void main(String[] args) {
try (EntityManagerFactory emf = Persistence.createEntityManagerFactory("blog")) {
emf.runInTransaction(em -> em.persist(new Post("Привет, Hibernate", "Моя первая статья")));
EntityManager em = emf.createEntityManager();
try {
em.getTransaction().begin();
Post a = em.find(Post.class, 1L);
Post b = em.find(Post.class, 1L);
IO.println("Один и тот же объект: " + (a == b));
a.setTitle("Привет снова");
IO.println("Заголовок изменён, фиксируем");
em.getTransaction().commit();
} finally {
em.close();
}
}
}
}Для этого примера добавьте в Post сеттер setTitle. Вывод:
Hibernate: insert into Post (body,title,id) values (?,?,?)
Hibernate: select p1_0.id,p1_0.body,p1_0.title from Post p1_0 where p1_0.id=?
Один и тот же объект: true
Заголовок изменён, фиксируем
Hibernate: update Post set body=?,title=? where id=?Что произошло
- Один запрос на два вызова. Первый
findзагрузил строку и положил объект в контекст персистентности. Второйfindнашёл его там и в базу не ходил. Поэтому контекст персистентности ещё называют кешем первого уровня. - Тот же самый объект. В пределах одного
EntityManagerодной строке всегда соответствует ровно один Java-объект, поэтомуa == b. - Обновление без вызова сохранения. При фиксации Hibernate сравнивает управляемые объекты с тем состоянием, которое загрузил, и записывает разницу. Это называется dirty checking — проверка изменений. Метода
save()илиupdate()нет: достаточно поменять поле управляемого объекта.
Session
У Hibernate есть собственные интерфейсы с теми же ролями: SessionFactory и Session. Они расширяют EntityManagerFactory и EntityManager и добавляют методы, которых нет в стандарте. Объект Hibernate всегда можно получить из стандартного:
// src/main/java/com/example/blog/Main.java
emf.runInTransaction(em -> {
Session session = em.unwrap(Session.class);
Post post = session.find(Post.class, 1L);
IO.println("В базе: " + post.getTitle());
});Напечатается В базе: Привет снова — изменение действительно сохранилось. Session импортируется из org.hibernate.
В Spring Boot
В Spring-приложении EntityManager самостоятельно не создают. Spring создаёт его на каждый запрос, а метод с аннотацией @Transactional получает транзакцию. Чаще всего к EntityManager даже не обращаются напрямую: его вызывают репозитории Spring Data.
Частая ошибка
EntityManager без транзакции ничего не сохраняет. persist молча запоминает объект, а при закрытии менеджера он просто забывается:
// src/main/java/com/example/blog/Main.java
EntityManager em = emf.createEntityManager();
em.persist(new Post("Потерянная статья", "Без транзакции"));
em.close();Подсчёт после этого покажет Статей: 0. Если попытаться принудительно записать изменения через em.flush(), будет ошибка:
Exception in thread "main" jakarta.persistence.TransactionRequiredException: No active transactionЛюбые изменения данных должны происходить между begin() и commit() — или внутри runInTransaction.