Как я объяснил в этой статье , вы должны предпочитать методы 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 автоматически обрабатывается во время сброса контекста постоянства.
Для более подробной информации, проверьте эту статью .