← Назад к вопросам
Как внутри устроен EntityManager
2.3 Middle🔥 121 комментариев
#ORM и Hibernate#Spring Framework
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как внутри устроен EntityManager
EntityManager — это центральный компонент JPA (Java Persistence API), который управляет жизненным циклом сущностей и взаимодействует с базой данных. Понимание его внутреннего устройства критично для эффективной работы.
Основные компоненты EntityManager
┌─────────────────────────────────────────┐
│ EntityManager (Interface) │
├─────────────────────────────────────────┤
│ Управляет жизненным циклом Entity │
│ - Кэширует объекты (1-level cache) │
│ - Отслеживает изменения │
│ - Генерирует SQL │
├─────────────────────────────────────────┤
│ Hiberate EntityManagerImpl (реализация) │
├─────────────────────────────────────────┤
│ Persistence Context (контекст) │
│ - Identity Map (Map объектов) │
│ - Snapshot (для отслеживания изменений)│
├─────────────────────────────────────────┤
│ Session (Hibernate компонент) │
├─────────────────────────────────────────┤
│ SQL Dialect (для конкретной БД) │
└─────────────────────────────────────────┘
Жизненный цикл Entity
public class EntityLifecycle {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("myPU");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
// 1. NEW (Transient) — объект не управляется EntityManager
User user = new User();
user.setEmail("john@example.com");
// Object в памяти, не в БД, не в Persistence Context
// 2. MANAGED (Persistent) — объект управляется EM
tx.begin();
em.persist(user); // Добавляем в Persistence Context
// Теперь EM отслеживает все изменения этого объекта
tx.commit();
// 3. Изменение MANAGED объекта
tx.begin();
user.setEmail("newemail@example.com");
// EM заметит это изменение (dirty checking)
// При commit() сгенерирует UPDATE SQL автоматически
tx.commit();
// 4. DETACHED — объект вышел из Persistence Context
em.close();
// user всё ещё существует в памяти, но EM не управляет им
// 5. Переподключение DETACHED объекта
em = emf.createEntityManager();
tx = em.getTransaction();
tx.begin();
user = em.merge(user); // merge() переводит в MANAGED
user.setFullName("John Doe");
tx.commit();
// 6. REMOVED (Deleted) — объект удалён
tx.begin();
user = em.find(User.class, userId);
em.remove(user); // Помечаем для удаления
tx.commit(); // DELETE SQL выполняется здесь
em.close();
emf.close();
}
}
Persistence Context (Identity Map)
Это кэш первого уровня, хранящий все управляемые объекты:
public class PersistenceContextExample {
public static void main(String[] args) {
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
// Первый find — запрос к БД
User user1 = em.find(User.class, 1L);
// SELECT * FROM users WHERE id = 1
// Второй find — из кэша Persistence Context
User user2 = em.find(User.class, 1L);
// Нет SQL запроса!
// Одинаковые объекты по ссылке
System.out.println(user1 == user2); // true (один объект в памяти)
// Все изменения отслеживаются
user1.setEmail("new@example.com");
// EM заметил изменение
tx.commit();
// UPDATE users SET email = 'new@example.com' WHERE id = 1
em.close();
}
}
Dirty Checking (Отслеживание изменений)
public class DirtyCheckingExample {
public static void main(String[] args) {
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
// Загружаем объект
User user = em.find(User.class, 1L);
// EM создаёт SNAPSHOT текущего состояния
// Меняем поле
user.setEmail("newemail@example.com");
// EM заметит: текущее состояние != snapshot
// Нет явного update() — EM сам понял об изменении
tx.commit();
// EM сравнивает current state с snapshot
// Генерирует UPDATE SQL
// UPDATE users SET email = 'newemail@example.com' WHERE id = 1
em.close();
}
}
Flush (Синхронизация с БД)
public class FlushExample {
public static void main(String[] args) {
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
User user = new User();
user.setEmail("john@example.com");
em.persist(user);
// user ещё не в БД — только в Persistence Context
// Явный flush (обычно автоматический при commit)
em.flush();
// Теперь: INSERT INTO users...
System.out.println("User id: " + user.getId());
// ID уже заполнен (получен от БД)
// Можем запросить объект из БД
User found = em.find(User.class, user.getId());
// Это тот же объект из Persistence Context
System.out.println(user == found); // true
tx.commit(); // Второй flush перед коммитом
em.close();
}
}
Lazy Loading и Fetch Strategies
@Entity
public class User {
@Id
private Long id;
private String email;
// LAZY — загружается только при обращении
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Order> orders;
// EAGER — загружается сразу с User
@ManyToOne(fetch = FetchType.EAGER)
private Company company;
}
public class LazyLoadingExample {
public static void main(String[] args) {
EntityManager em = emf.createEntityManager();
// Загружаем User
User user = em.find(User.class, 1L);
// SELECT * FROM users WHERE id = 1
// company загружается (EAGER)
// SELECT * FROM companies WHERE id = ...
// orders ещё не загружены
// Обращаемся к orders
List<Order> orders = user.getOrders();
// Теперь EM генерирует SQL
// SELECT * FROM orders WHERE user_id = 1
em.close();
}
}
N+1 Problem и решение
// ❌ ПРОБЛЕМА
public void problematicApproach() {
EntityManager em = emf.createEntityManager();
List<User> users = em.createQuery(
"SELECT u FROM User u", User.class
).getResultList();
// Запрос 1: SELECT * FROM users
for (User user : users) {
List<Order> orders = user.getOrders();
// Запросы 2, 3, 4... = 1 + N запросов
}
}
// ✅ РЕШЕНИЕ 1: JOIN FETCH
public void solutionJoinFetch() {
EntityManager em = emf.createEntityManager();
List<User> users = em.createQuery(
"SELECT DISTINCT u FROM User u LEFT JOIN FETCH u.orders",
User.class
).getResultList();
// 1 запрос с JOIN
}
// ✅ РЕШЕНИЕ 2: Explicit JOIN
public void solutionEntityGraph() {
EntityManager em = emf.createEntityManager();
EntityGraph<User> graph = em.createEntityGraph(User.class);
graph.addAttributeNodes("orders");
List<User> users = em.createQuery(
"SELECT u FROM User u", User.class
).setHint("javax.persistence.fetchgraph", graph)
.getResultList();
}
Кэширование и Query Cache
public class CachingExample {
public static void main(String[] args) {
EntityManager em = emf.createEntityManager();
// 1st level cache (Persistence Context)
User user1 = em.find(User.class, 1L);
User user2 = em.find(User.class, 1L);
System.out.println(user1 == user2); // true
// Query cache (отключен по умолчанию)
TypedQuery<User> query = em.createQuery(
"SELECT u FROM User u WHERE u.email = :email",
User.class
);
query.setParameter("email", "john@example.com");
query.setHint("org.hibernate.cacheable", true);
User user3 = query.getSingleResult();
// Результат может быть закэширован
}
}
Batch Operations
public class BatchOperations {
public static void main(String[] args) {
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
// Без batch
for (int i = 0; i < 1000; i++) {
User user = new User("user" + i);
em.persist(user);
// После 1000 итераций — 1000 INSERT запросов
}
// С batch (hibernate.jdbc.batch_size=50)
// INSERT группируются: 1000 INSERTs → 20 батчей по 50
tx.commit();
em.close();
}
}
Внутренняя структура Session (Hibernate)
// EntityManager.getDelegate() даёт доступ к Session
Session session = em.unwrap(Session.class);
// StatisticsFactory для отладки
Statistics stats = sessionFactory.getStatistics();
stats.logSummary();
// Показывает: количество запросов, cache hits, коллекции загруженные
Context Lifecycle в Spring
@Transactional // Автоматически управляет EntityManager
public class UserService {
@Autowired
private UserRepository repository;
public void updateUser(Long id) {
// На начало @Transactional
// Spring создаёт EntityManager
// Начинается Persistence Context
User user = repository.findById(id).get();
// EntityManager управляет user
user.setEmail("new@example.com");
// Dirty checking отслеживает изменение
// На конец метода
// Spring вызывает em.flush()
// Вызывает em.close()
// Persistence Context закрывается
}
}
Заключение
EntityManager — это мощный инструмент, который:
- Управляет объектами через Persistence Context
- Отслеживает изменения (dirty checking)
- Кэширует объекты (1st level cache)
- Оптимизирует SQL через батчинг
- Управляет жизненным циклом Entity
- Генерирует SQL автоматически
Понимание его внутреннего устройства поможет писать эффективный и правильный ORM код.