Як я пояснив у цій статті , ви повинні віддавати перевагу методам JPA більшість часу та завданням updateпакетної обробки.
Спільна спільна особа або сплячий суб'єкт може перебувати в одному з наступних чотирьох штатів:
- Тимчасовий (новий)
- Керований (стійкий)
- Окремі
- Видалено (видалено)
Перехід від одного стану до іншого здійснюється методами EntityManager або Session.
Наприклад, JPA EntityManagerнадає такі методи переходу стану сутності.

У Hibernate Sessionреалізує всі JPA EntityManagerметоди і надає деякі додаткові методи суб'єкт перехідного стану , як save, saveOrUpdateі update.

Персист
Щоб змінити стан суб'єкта господарювання з Тимчасового (Нового) на Керованого (Персистентного), ми можемо використовувати persistметод, запропонований JPA, EntityManagerякий також успадковується Гібернатом Session.
persistМетод викликає , PersistEventякий обробляється DefaultPersistEventListenerПрослуховувач подій глибокого сну.
Тому при виконанні наступного тестового випадку:
doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
LOGGER.info(
"Persisting the Book entity with the id: {}",
book.getId()
);
});
Hibernate генерує такі оператори SQL:
CALL NEXT VALUE FOR hibernate_sequence
-- Persisting the Book entity with the id: 1
INSERT INTO book (
author,
isbn,
title,
id
)
VALUES (
'Vlad Mihalcea',
'978-9730228236',
'High-Performance Java Persistence',
1
)
Зауважте, що idприсвоюється до приєднання Bookсутності до поточного контексту постійності. Це потрібно, оскільки керовані об'єкти зберігаються в Mapструктурі, де ключ формується за типом сутності та його ідентифікатором, а значення - посиланням на сутність. Це причина, через яку JPA EntityManagerі Hibernate Sessionвідомі як кеш першого рівня.
Під час виклику persistсутність приєднується лише до поточно працюючого контексту постійності, і INSERT можна відкласти до виклику flush.
Єдиним винятком є генератор IDENTITY, який спрацьовує INSERT відразу, оскільки це єдиний спосіб отримати ідентифікатор сутності. З цієї причини Hibernate не може додати вставки для об'єктів, що використовують генератор IDENTITY. Детальніше про цю тему ознайомтеся з цією статтею .
Зберегти
Метод, специфічний для saveспячки, передує JPA, і він доступний з початку проекту "Зимовий режим".
saveМетод викликає , SaveOrUpdateEventякий обробляється DefaultSaveOrUpdateEventListenerПрослуховувач подій глибокого сну. Тому saveспосіб еквівалентний методам updateі saveOrUpdate.
Щоб побачити, як saveпрацює метод, розгляньте наступний тестовий випадок:
doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
Session session = entityManager.unwrap(Session.class);
Long id = (Long) session.save(book);
LOGGER.info(
"Saving the Book entity with the id: {}",
id
);
});
Під час запуску тестового випадку вище, Hibernate генерує такі оператори SQL:
CALL NEXT VALUE FOR hibernate_sequence
-- Saving the Book entity with the id: 1
INSERT INTO book (
author,
isbn,
title,
id
)
VALUES (
'Vlad Mihalcea',
'978-9730228236',
'High-Performance Java Persistence',
1
)
Як бачите, результат ідентичний persistвиклику методу. Однак, на відміну від цього persist, saveметод повертає ідентифікатор сутності.
Більш детально ознайомтеся з цією статтею .
Оновлення
Метод, специфічний для updateспячки, призначений для обходу брудного механізму перевірки та примушування оновлення суб'єкта господарювання під час потоку.
updateМетод викликає , SaveOrUpdateEventякий обробляється DefaultSaveOrUpdateEventListenerПрослуховувач подій глибокого сну. Тому updateспосіб еквівалентний методам saveі saveOrUpdate.
Щоб побачити, як updateпрацює метод, розглянемо наступний приклад, який зберігає Bookоб'єкт в одній транзакції, потім він модифікує його, коли сутність знаходиться у відокремленому стані, і він змушує SQL UPDATE використовувати updateвиклик методу.
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
return book;
});
LOGGER.info("Modifying the Book entity");
_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:
CALL NEXT VALUE FOR hibernate_sequence
INSERT INTO book (
author,
isbn,
title,
id
)
VALUES (
'Vlad Mihalcea',
'978-9730228236',
'High-Performance Java Persistence',
1
)
-- Modifying the Book entity
-- Updating the Book entity
UPDATE
book
SET
author = 'Vlad Mihalcea',
isbn = '978-9730228236',
title = 'High-Performance Java Persistence, 2nd edition'
WHERE
id = 1
Зауважте, що UPDATEфункція виконується під час спалаху контексту стійкості безпосередньо перед фіксацією, і саме тому Updating the Book entityповідомлення спочатку реєструється.
Використання, @SelectBeforeUpdateщоб уникнути зайвих оновлень
Тепер ОНОВЛЕННЯ завжди буде виконано, навіть якщо сутність не була змінена під час перебування в окремому стані. Щоб цього не допустити, ви можете використовувати @SelectBeforeUpdateанотацію Hibernate, яка спричинить отримання SELECTтвердження, loaded stateяке потім використовується брудним механізмом перевірки.
Отже, якщо ми коментуємо Bookсутність @SelectBeforeUpdateанотацією:
@Entity(name = "Book")
@Table(name = "book")
@SelectBeforeUpdate
public class Book {
//Code omitted for brevity
}
І виконати такий тестовий випадок:
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
return book;
});
doInJPA(entityManager -> {
Session session = entityManager.unwrap(Session.class);
session.update(_book);
});
Hibernate виконує такі оператори SQL:
INSERT INTO book (
author,
isbn,
title,
id
)
VALUES (
'Vlad Mihalcea',
'978-9730228236',
'High-Performance Java Persistence',
1
)
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
Зауважте, що цього разу не UPDATEвиконується, оскільки механізм брудної перевірки в сплячому режимі виявив, що сутність не була змінена.
SaveOrUpdate
Специфічний для сплячки saveOrUpdateметод - лише псевдонім для saveта update.
saveOrUpdateМетод викликає , SaveOrUpdateEventякий обробляється DefaultSaveOrUpdateEventListenerПрослуховувач подій глибокого сну. Тому updateспосіб еквівалентний методам saveі saveOrUpdate.
Тепер ви можете використовувати, saveOrUpdateколи хочете зберегти сутність або змусити форму, 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");
doInJPA(entityManager -> {
Session session = entityManager.unwrap(Session.class);
session.saveOrUpdate(_book);
});
Остерігайся NonUniqueObjectException
Одна проблема, яка може виникнути з save, updateі saveOrUpdateполягає в тому, якщо контекст стійкості вже містить посилання на сутність з тим же ідентифікатором і того ж типу, що і в наступному прикладі:
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 збирається кинути знак a, NonUniqueObjectExceptionоскільки другий EntityManagerвже містить aBook сутність з тим самим ідентифікатором, що і той, до якого ми передаємо 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)
Злиття
Щоб уникнути цього NonUniqueObjectException, потрібно використовувати mergeметод, запропонований JPAEntityManager та успадкований також сплячим Session.
Як пояснено в цій статті , mergeвитягує новий знімок об'єкта з бази даних, якщо в контексті постійності не знайдено посилання на об'єкт, і він копіює стан відокремленої сутності, переданий наmerge методу.
mergeМетод викликає , MergeEventякий обробляєтьсяDefaultMergeEventListenerПрослуховувач подій глибокого сну.
Щоб побачити, як mergeпрацює метод, розглянемо наступний приклад, який зберігає Bookсуб'єкт господарювання в одній транзакції, потім він модифікує його, поки суб'єкт знаходиться в відірваному стані, і передає від'єднану сутність mergeв послідовність послідовності послідовності.
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
return book;
});
LOGGER.info("Modifying the Book entity");
_book.setTitle(
"High-Performance Java Persistence, 2nd edition"
);
doInJPA(entityManager -> {
Book book = entityManager.merge(_book);
LOGGER.info("Merging the Book entity");
assertFalse(book == _book);
});
Під час запуску тестового випадку, Hibernate виконав такі оператори SQL:
INSERT INTO book (
author,
isbn,
title,
id
)
VALUES (
'Vlad Mihalcea',
'978-9730228236',
'High-Performance Java Persistence',
1
)
-- Modifying the Book entity
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
Зауважте, що повернення сутності, що повертається, mergeвідрізняється від відокремленої, яку ми передали mergeметоду.
Тепер, хоча вам слід віддати перевагу використанню JPA mergeпід час копіювання окремого стану сутності, додаткове SELECTможе бути проблематичним при виконанні завдання пакетної обробки.
З цієї причини вам слід віддати перевагу використанню update коли ви впевнені, що до поточного контексту персистенції вже не додано посилання на сутність та що відокремлена сутність була змінена.
Детальніше про цю тему ознайомтеся з цією статтею .
Висновок
Щоб зберегти сутність, слід скористатися persistметодом JPA . Щоб скопіювати відокремлений стан сутності,merge слід віддати перевагу. updateМетод корисний тільки для завдань пакетної обробки. saveІ saveOrUpdateпросто псевдонімиupdate , і ви не повинні , ймовірно , використовувати їх на всіх.
Деякі розробники телефонують save навіть тоді, коли суттю вже керовано, але це помилка і викликає зайву подію, оскільки для керованих об'єктів UPDATE автоматично обробляється під час змивання контексту Persistent.
Більш детально ознайомтеся з цією статтею .