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

Когда происходит запись нескольких сущностей в Persistence Context?

3.0 Senior🔥 131 комментариев
#ORM и Hibernate

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Когда происходит запись в Persistence Context

Persistence Context — это кэш сущностей в памяти приложения. Понимание моментов записи критично для работы с Hibernate и JPA.

Основной момент: flush()

Сущности записываются в БД при вызове flush(). Это не commit, а отправка SQL команд.

public class PersistenceContextExample {
    @Autowired
    private EntityManager em;
    
    public void demonstrateFlush() {
        // 1. Создаем сущность (в памяти, SQL еще нет)
        Author author = new Author("John Doe");
        em.persist(author);  // Добавлена в Persistence Context
        // SQL INSERT еще не выполнен!
        
        // 2. Изменяем сущность
        author.setName("Jane Doe");
        
        // 3. Flush() — происходит запись в БД
        em.flush();  // Теперь выполнены INSERT и UPDATE
        
        // 4. Commit() — фиксирует транзакцию
        // em.getTransaction().commit();
    }
}

Когда автоматически вызывается flush()?

1. При commit() транзакции (САМЫЙ ЧАСТЫЙ СЛУЧАЙ)

@Transactional  // Аннотация Spring
public void createAuthorsAndBooks() {
    Author author1 = new Author("Isaac Asimov");
    Author author2 = new Author("Arthur Clarke");
    
    em.persist(author1);
    em.persist(author2);
    
    Book book = new Book("Foundation", author1);
    em.persist(book);
    
    // Здесь автоматически вызывается flush() и затем commit()
    // Все 3 INSERT выполняются в БД
}
// При выходе из @Transactional метода:
// 1. Вызывается flush()
// 2. Затем commit()

Это 80% случаев — flush происходит при завершении транзакции.

2. При выполнении SQL запроса внутри транзакции

@Transactional
public void flushBeforeQuery() {
    Author author = new Author("Philip K. Dick");
    em.persist(author);  // В Persistence Context
    
    // Выполняем JPQL запрос
    List<Author> authors = em.createQuery(
        "SELECT a FROM Author a WHERE a.name = 'Philip K. Dick'",
        Author.class
    ).getResultList();
    
    // Hibernate автоматически вызвал flush() ПЕРЕД запросом!
    // Иначе мы не получили бы только что сохраненного автора
    // Результат: authors содержит нашего только что созданного автора
}

Это важно помнить: перед любым SELECT автоматически вызывается flush().

3. При явном вызове flush()

@Transactional
public void explicitFlush() {
    Author author = new Author("Frank Herbert");
    em.persist(author);
    
    em.flush();  // Явно отправляем в БД
    System.out.println(author.getId());  // ID уже установлен!
    
    // Но если тут произойдет исключение,
    // транзакция откатится и INSERT не будет сохранен
}

4. При работе с нативными SQL запросами

@Transactional
public void nativeQuery() {
    Author author = new Author("N.K. Jemisin");
    em.persist(author);
    
    // Нативный SQL запрос
    em.createNativeQuery(
        "SELECT * FROM authors WHERE name = ?"
    ).setParameter(1, "N.K. Jemisin").getResultList();
    
    // Hibernate вызовет flush() перед выполнением нативного запроса
    // Это может быть медленным для больших Persistence Context
}

Несколько сущностей одновременно

@Transactional
public void saveMultipleEntities() {
    // Добавляем несколько сущностей
    for (int i = 0; i < 1000; i++) {
        Author author = new Author("Author " + i);
        em.persist(author);
    }
    
    // ВСЕ 1000 сущностей находятся в Persistence Context
    // Это занимает память!
    
    // При commit() все 1000 INSERT выполнятся ОДНОЙ ночью (или батчами)
    // благодаря параметру: spring.jpa.properties.hibernate.jdbc.batch_size
}

Управление flush mode

@Transactional
public void flushModes() {
    Author author = new Author("Ray Bradbury");
    em.persist(author);
    
    // AUTO (по умолчанию) — flush перед SELECT
    // COMMIT — flush только при commit
    // MANUAL — только при явном вызове flush()
    
    em.setFlushMode(FlushModeType.COMMIT);
    
    List<Authors> authors = em.createQuery(
        "SELECT a FROM Author a",
        Author.class
    ).getResultList();
    // Ray Bradbury может быть не в результатах, потому что
    // flush еще не произошел (FlushModeType.COMMIT)
}

Практические следствия

Проблема: Persistence Context слишком большой

@Transactional
public void processMillion() {
    for (int i = 0; i < 1_000_000; i++) {
        Author author = new Author("Author " + i);
        em.persist(author);
        
        if (i % 1000 == 0) {
            em.flush();  // Отправляем батч в БД
            em.clear();  // Очищаем кэш (ВАЖНО!)
        }
    }
}

Без em.clear() все 1 млн сущностей останутся в памяти, потребляя ГБ ОЗУ!

Проблема: Lost updates

@Transactional
public void concurrentUpdate() {
    Author author = em.find(Author.class, 1L);  // Версия 1
    author.setName("New Name");
    
    // Другой поток обновил того же автора
    // Когда произойдет flush():
    // - Наша версия 1, в БД уже версия 2
    // - Будет OptimisticLockException
}

Ключевые точки

  • Flush != Commit — flush отправляет SQL, commit фиксирует транзакцию
  • Автоматический flush при SELECT, commit, явном вызове
  • Batch size оптимизирует отправку нескольких INSERT в одном запросе
  • Em.clear() освобождает память при обработке больших объемов

Вывод: Запись нескольких сущностей происходит при flush(), который вызывается автоматически при commit() или SELECT, или явно через em.flush(). Это один из самых важных концептов в Hibernate.