Як я пояснив у цій статті , ви повинні віддавати перевагу методам 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.
Більш детально ознайомтеся з цією статтею .