Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Состояния сущности

Объект-сущность всегда находится в одном из четырёх состояний. От состояния зависит, отслеживает ли Hibernate изменения объекта и что с ним сделает каждый метод EntityManager. Большинство непонятных ситуаций — «поменял поле, а ничего не сохранилось» — объясняются тем, что объект находится не в том состоянии, о котором вы думаете.

Четыре состояния

СостояниеЧто значитКак в него попадает
Новый (transient)Только что создан через new, Hibernate о нём не знает, обычно без idnew Post(...)
Управляемый (managed)В контексте персистентности: изменения сохраняются самиpersist, find, запрос, результат merge
Отсоединённый (detached)Есть id и строка в базе, но его EntityManager закрытзакрытие EntityManager, detach, clear
Удалённый (removed)Помечен на удаление, строка исчезнет при фиксацииremove

Следим за одним объектом

// src/main/java/com/example/blog/Main.java
package com.example.blog;
 
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;
 
public class Main {
    public static void main(String[] args) {
        try (EntityManagerFactory emf = Persistence.createEntityManagerFactory("blog")) {
            Post post = new Post("Состояния", "Новый, управляемый, отсоединённый, удалённый");
            IO.println("1. новый: id = " + post.getId());
 
            emf.runInTransaction(em -> {
                em.persist(post);
                IO.println("2. управляемый: contains = " + em.contains(post) + ", id = " + post.getId());
            });
 
            post.setTitle("Изменён после отсоединения");
            String title = emf.callInTransaction(em -> em.find(Post.class, post.getId()).getTitle());
            IO.println("3. отсоединённый: в базе по-прежнему '" + title + "'");
 
            emf.runInTransaction(em -> {
                Post managed = em.merge(post);
                IO.println("4. merge вернул тот же объект: " + (managed == post));
                em.remove(managed);
                IO.println("5. удалённый: contains = " + em.contains(managed));
            });
        }
    }
}

em.contains(object) отвечает, находится ли объект в этом контексте персистентности. Вывод:

1. новый: id = null
Hibernate: select next value for Post_SEQ
2. управляемый: contains = true, id = 1
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=?
3. отсоединённый: в базе по-прежнему 'Состояния'
Hibernate: select p1_0.id,p1_0.body,p1_0.title from Post p1_0 where p1_0.id=?
4. merge вернул тот же объект: false
5. удалённый: contains = false
Hibernate: delete from Post where id=?

Что показывает вывод

  1. У нового объекта нет id: Hibernate его ещё не видел.
  2. После persist объект управляемый и уже получил id.
  3. Транзакция закончилась, EntityManager закрылся, и объект стал отсоединённым. Смена заголовка затронула только Java-объект — никакого update не было.
  4. merge не присоединяет переданный объект. Он загружает управляемую копию, переносит в неё поля и возвращает копию. Оригинал так и остаётся отсоединённым.
  5. После remove объекта больше нет в контексте, а delete выполняется при фиксации.

В веб-приложении

Отсоединённые объекты в веб-приложениях — обычное дело: сущность, загруженная в одном запросе, живёт в форме и возвращается в другом запросе. Либо загрузите сущность заново через find и перенесите в неё новые значения, либо передайте отсоединённый объект в merge.

Частая ошибка

persist — только для новых объектов. Если передать ему отсоединённый объект — у него уже есть id, — Hibernate откажется:

// src/main/java/com/example/blog/Main.java
post.setTitle("Исправлено");
emf.runInTransaction(em -> em.persist(post));
Exception in thread "main" jakarta.persistence.EntityExistsException: Detached entity passed to persist: com.example.blog.Post

Чтобы сохранить изменения объекта, который уже есть в базе, используйте merge.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.