← Назад к вопросам

Как внутри устроен 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 — это мощный инструмент, который:

  1. Управляет объектами через Persistence Context
  2. Отслеживает изменения (dirty checking)
  3. Кэширует объекты (1st level cache)
  4. Оптимизирует SQL через батчинг
  5. Управляет жизненным циклом Entity
  6. Генерирует SQL автоматически

Понимание его внутреннего устройства поможет писать эффективный и правильный ORM код.

Как внутри устроен EntityManager | PrepBro