Как я объяснил в этой статье , вы должны предпочитать методы JPA большую часть времени, иupdate для задач пакетной обработки.
Объект JPA или Hibernate может находиться в одном из следующих четырех состояний:
- Переходный (Новый)
- Управляемый (Постоянный)
- отдельный
- Удалено (Удалено)
Переход из одного состояния в другое осуществляется с помощью методов EntityManager или Session.
Например, JPA EntityManagerпредоставляет следующие методы перехода состояния объекта.

Hibernate Sessionреализует все EntityManagerметоды JPA и предоставляет некоторые дополнительные методы перехода состояния объекта, такие как save, saveOrUpdateи update.

упорствовать
Чтобы изменить состояние сущности с Transient (New) на Managed (Persisted), мы можем использовать persistметод, предложенный JPA, EntityManagerкоторый также наследуется Hibernate 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. Для более подробной информации по этой теме, проверьте эту статью .
Сохранить
Специфичный для Hibernate saveметод предшествует JPA, и он доступен с начала проекта Hibernate.
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метод возвращает идентификатор объекта.
Для более подробной информации, проверьте эту статью .
Обновить
Специфичный для Hibernate updateметод предназначен для обхода механизма грязной проверки и принудительного обновления сущности во время сброса.
updateМетод вызывает , SaveOrUpdateEventкоторый обрабатывается DefaultSaveOrUpdateEventListenerпрослушивателя событий гибернации. Таким образом, updateметод эквивалентен saveи saveOrUpdateметоды.
Чтобы увидеть, как updateработает метод, рассмотрим следующий пример, в котором Bookсущность сохраняется в одной транзакции, затем она модифицирует ее, пока сущность находится в отсоединенном состоянии, и updateвызывает SQL 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выполняется, так как механизм грязной проверки Hibernate обнаружил, что объект не был изменен.
SaveOrUpdate
Специфичный для Hibernate 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уже содержит 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)
Объединить
Чтобы избежать этого NonUniqueObjectException, вам нужно использовать mergeметод, предложенный JPA EntityManagerи унаследованный Hibernate 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 автоматически обрабатывается во время сброса контекста постоянства.
Для более подробной информации, проверьте эту статью .