Состояния сущности
Объект-сущность всегда находится в одном из четырёх состояний. От состояния зависит, отслеживает ли Hibernate изменения объекта и что с ним сделает каждый метод EntityManager. Большинство непонятных ситуаций — «поменял поле, а ничего не сохранилось» — объясняются тем, что объект находится не в том состоянии, о котором вы думаете.
Четыре состояния
| Состояние | Что значит | Как в него попадает |
|---|---|---|
| Новый (transient) | Только что создан через new, Hibernate о нём не знает, обычно без id | new 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=?Что показывает вывод
- У нового объекта нет id: Hibernate его ещё не видел.
- После
persistобъект управляемый и уже получил id. - Транзакция закончилась,
EntityManagerзакрылся, и объект стал отсоединённым. Смена заголовка затронула только Java-объект — никакогоupdateне было. mergeне присоединяет переданный объект. Он загружает управляемую копию, переносит в неё поля и возвращает копию. Оригинал так и остаётся отсоединённым.- После
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.