Оскільки це дуже поширене питання, я написав
цю статтю , на якій ґрунтується ця відповідь.
Сутні держави
JPA визначає наступні сутності:
Нове (тимчасове)
Новостворений об’єкт, який ніколи не асоціювався зі сплячим режимом Session
(ака Persistence Context
) і не відображається в жодному рядку таблиці баз даних, вважається в новому (перехідному) стані.
Щоб стати наполегливим, нам потрібно або явно викликати EntityManager#persist
метод, або скористатися механізмом перехідної стійкості.
Постійний (керований)
Послідовна сутність була пов'язана з рядком таблиці баз даних, і нею керує поточний контекст постійності. Будь-які зміни, внесені до такої сутності, будуть виявлені та розповсюджені до бази даних (під час часу сесії).
У режимі Hibernate нам більше не потрібно виконувати заяви INSERT / UPDATE / DELETE. У сплячому режимі використовується стиль роботи транзакційного запису, і зміни синхронізуються в самий останній відповідальний момент, під час поточного Session
потоку.
Окремі
Після закриття поточного контексту постійності всі раніше керовані об'єкти відключаються. Послідовні зміни більше не відстежуються, і автоматична синхронізація бази даних не відбуватиметься.
Переходи сутності держави
Ви можете змінити стан сутності за допомогою різних методів, визначених EntityManager
інтерфейсом.
Щоб краще зрозуміти переходи стану сутності JPA, врахуйте наступну схему:
Використовуючи JPA, щоб пов’язати відокремлену сутність з активною EntityManager
, ви можете використовувати операцію злиття .
При використанні нативного API Hibernate, крім merge
, ви можете повторно приєднати відокремлену сутність до активної гібернатової сесії, використовуючи методи оновлення, як показано на наступній схемі:
Об'єднання відокремленої сутності
Злиття збирається скопіювати відокремлений стан об'єкта (джерело) в керований екземпляр об'єкта (призначення).
Подумайте, що ми зберігаємо наступну Book
сутність, і тепер ця структура відокремлена як та, EntityManager
яка використовувалася для збереження закритої структури:
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
return book;
});
Поки сутність знаходиться у відокремленому стані, ми змінюємо її наступним чином:
_book.setTitle(
"High-Performance Java Persistence, 2nd edition"
);
Тепер ми хочемо поширити зміни в базі даних, щоб ми могли викликати merge
метод:
doInJPA(entityManager -> {
Book book = entityManager.merge(_book);
LOGGER.info("Merging the Book entity");
assertFalse(book == _book);
});
І Hibernate збирається виконати такі оператори SQL:
SELECT
b.id,
b.author AS author2_0_,
b.isbn AS isbn3_0_,
b.title AS title4_0_
FROM
book b
WHERE
b.id = 1
-- Merging the Book entity
UPDATE
book
SET
author = 'Vlad Mihalcea',
isbn = '978-9730228236',
title = 'High-Performance Java Persistence, 2nd edition'
WHERE
id = 1
Якщо об'єднання об'єднання не має еквівалента в поточному EntityManager
, з бази даних буде отримано свіжий знімок об'єкта.
Після того, як є керована сутність, JPA копіює стан відокремленої сутності на той, яким керується в даний час, і під час контекстуflush
персистенції буде створено ОНОВЛЕННЯ, якщо брудний механізм перевірки виявить, що керована сутність змінилася.
Отже, при використанні merge
відокремлений екземпляр об'єкта буде продовжувати залишатися відокремленим навіть після операції злиття.
Повторне з'єднання відокремленої сутності
Перебуває в сплячому режимі, але не JPA підтримує повторне приєднання за допомогою update
методу.
Hibernate Session
може асоціювати лише один об'єкт сутності для заданого рядка бази даних. Це пояснюється тим, що контекст стійкості діє як кеш пам'яті (кеш першого рівня), і лише одне значення (сутність) пов'язане з заданим ключем (тип сутності та ідентифікатор бази даних).
Суб'єкт можна повторно приєднати, лише якщо немає жодного іншого об'єкта JVM (відповідного тому ж рядку бази даних), який вже пов'язаний з поточним режимом сплячого режиму Session
.
Зважаючи на те, що ми зберігаємо Book
сутність і що ми її змінили, коли Book
суб'єкт господарювання був у відокремленому стані:
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
return book;
});
_book.setTitle(
"High-Performance Java Persistence, 2nd edition"
);
Ми можемо повторно приєднати відокремлену суть так:
doInJPA(entityManager -> {
Session session = entityManager.unwrap(Session.class);
session.update(_book);
LOGGER.info("Updating the Book entity");
});
І Hibernate виконає такий оператор SQL:
-- Updating the Book entity
UPDATE
book
SET
author = 'Vlad Mihalcea',
isbn = '978-9730228236',
title = 'High-Performance Java Persistence, 2nd edition'
WHERE
id = 1
update
Метод вимагає , щоб ви в сплячий режим .unwrap
EntityManager
Session
На відміну від цього merge
, відокремлене об'єкт буде узгоджено з поточним контекстом постійності, і UPDATE планується під час флеш, незалежно від того, змінила чи ні.
Щоб уникнути цього, ви можете використовувати @SelectBeforeUpdate
анотацію Hibernate, яка спричинить операцію SELECT, що отримав завантажений стан, який потім використовується брудним механізмом перевірки.
@Entity(name = "Book")
@Table(name = "book")
@SelectBeforeUpdate
public class Book {
//Code omitted for brevity
}
Остерігайтеся NonUniqueObjectException
Одна з проблем, яка може виникнути, update
полягає в тому, якщо контекст стійкості вже містить посилання на об'єкт з тим же ідентифікатором і того ж типу, що і в наступному прикладі:
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
Session session = entityManager.unwrap(Session.class);
session.saveOrUpdate(book);
return book;
});
_book.setTitle(
"High-Performance Java Persistence, 2nd edition"
);
try {
doInJPA(entityManager -> {
Book book = entityManager.find(
Book.class,
_book.getId()
);
Session session = entityManager.unwrap(Session.class);
session.saveOrUpdate(_book);
});
} catch (NonUniqueObjectException e) {
LOGGER.error(
"The Persistence Context cannot hold " +
"two representations of the same entity",
e
);
}
Тепер, виконуючи тестовий випадок, описаний вище, Hibernate збирається кинути а, NonUniqueObjectException
оскільки друге EntityManager
вже містить Book
сутність з тим самим ідентифікатором, що і той, до якого ми передаємо update
, а Контекст стійкості не може містити двох представлень того самого об'єкта.
org.hibernate.NonUniqueObjectException:
A different object with the same identifier value was already associated with the session : [com.vladmihalcea.book.hpjp.hibernate.pc.Book#1]
at org.hibernate.engine.internal.StatefulPersistenceContext.checkUniqueness(StatefulPersistenceContext.java:651)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.java:284)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.java:227)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:92)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73)
at org.hibernate.internal.SessionImpl.fireSaveOrUpdate(SessionImpl.java:682)
at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:674)
Висновок
Цей merge
метод слід віддати перевагу, якщо ви використовуєте оптимістичне блокування, оскільки це дозволяє запобігти втраченим оновленням. Більш детально про цю тему перегляньте цю статтю .
Це update
добре для пакетних оновлень, оскільки може запобігти додатковому оператору SELECT, що генерується merge
операцією, тому скорочує час виконання пакетного оновлення.
refresh()
для відокремлених організацій? Переглядаючи специфікацію 2.0, я не бачу жодного виправдання; тільки що це не дозволено.