Как я объяснил в этой статье , вам следует чаще использовать методы JPA и update
для задач пакетной обработки.
Объект JPA или Hibernate может находиться в одном из следующих четырех состояний:
- Переходный (Новый)
- Управляемый (Постоянный)
- Отдельный
- Удалено (удалено)
Переход из одного состояния в другое осуществляется с помощью методов EntityManager или Session.
Например, JPA EntityManager
предоставляет следующие методы перехода состояний объекта.
Hibernate Session
реализует все методы JPA EntityManager
и предоставляет некоторые дополнительные методы перехода состояния объекта, такие как save
, saveOrUpdate
и update
.
.
Упорство
Чтобы изменить состояние объекта с Переходного (Новое) на Управляемое (Постоянное), мы можем использовать метод persist
, предлагаемый JPA EntityManager
, который также наследуется Hibernate Session
.
Метод persist
вызывает PersistEvent
, который обрабатывается прослушивателем событий DefaultPersistEventListener
Hibernate.
Поэтому при выполнении следующего контрольного примера:
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
, характерный для Hibernate, предшествует JPA и доступен с начала проекта Hibernate.
Метод save
вызывает SaveOrUpdateEvent
, который обрабатывается прослушивателем событий Hibernate 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
Hibernate. Следовательно, метод 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
не выполняется, так как механизм грязной проверки Hibernate обнаружил, что объект не был изменен.
SaveOrUpdate
Специфичный для Hibernate метод saveOrUpdate
- это просто псевдоним для save
и update
.
Метод saveOrUpdate
вызывает SaveOrUpdateEvent
, который обрабатывается прослушивателем событий Hibernate 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 собирается выбросить 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
Hibernate.
Чтобы увидеть, как работает метод 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
, если вы уверены, что к текущему контексту персистентности уже не привязана ссылка на сущность и что отсоединенная сущность была изменена.
Подробнее об этой теме читайте в этой статье .
Заключение
Чтобы сохранить сущность, вы должны использовать метод JPA persist
. Чтобы скопировать состояние отсоединенного объекта, merge
должно быть предпочтительным. Метод update
полезен только для задач пакетной обработки. save
и saveOrUpdate
являются просто псевдонимами update
, и вы, вероятно, не должны их использовать вообще.
Некоторые разработчики вызывают save
даже тогда, когда сущность уже управляется, но это ошибка и вызывает избыточное событие, поскольку для управляемых сущностей UPDATE автоматически обрабатывается во время сброса контекста постоянства.
Для получения более подробной информации, ознакомьтесь с этой статьей .