Автор оригинала: Vlad Mihalcea.
В этой статье я собираюсь показать вам, как работают JPA persist
и merge
и как они сравниваются с методами Hibernate save
, update
и saveOrUpdate
.
Хотя вы должны отдавать предпочтение методам перехода состояния сущности на основе JPA, вы увидите, что специфичный для гибернации обновление на самом деле является хорошей альтернативой
слиянию , когда вы хотите уменьшить количество SQL-запросов, выполняемых во время задачи пакетной обработки.
Как я объяснил в этой статье , объект JPA или Hibernate может находиться в одном из следующих четырех состояний:
- Переходный период (Новый)
- Управляемый (Постоянный)
- Отдельный
- Удалено (Удалено)
Переход из одного состояния в другое осуществляется с помощью методов EntityManager
или Сеанса
.
Например, JPA EntityManager
предоставляет следующие методы перехода состояния сущности.
Hibernate Сеанс
реализует все методы JPA EntityManager
и предоставляет некоторые дополнительные методы перехода состояния сущности, такие как сохранить
, Сохранить обновление
и обновление
.
Давайте рассмотрим, что у нас есть следующая Книга
сущность, которая использует API свободного стиля :
@Entity(name = "Book") @Table(name = "book") public class Book { @Id @GeneratedValue private Long id; private String isbn; private String title; private String author; public Long getId() { return id; } public Book setId(Long id) { this.id = id; return this; } public String getIsbn() { return isbn; } public Book setIsbn(String isbn) { this.isbn = isbn; return this; } public String getTitle() { return title; } public Book setTitle(String title) { this.title = title; return this; } public String getAuthor() { return author; } public Book setAuthor(String author) { this.author = author; return this; } }
Теперь давайте посмотрим, как мы можем сохранять и обновлять объект с помощью JPA и гибернации.
Чтобы изменить состояние сущности с переходного (Нового) на Управляемое (Сохраняемое), мы можем использовать метод persist
, предлагаемый JPA EntityManager
, который также наследуется сеансом Hibernate /.
Метод persist
запускает Постоянное событие
, которое обрабатывается 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 )
Обратите внимание, что идентификатор
присваивается до присоединения Книги
сущности к текущему контексту сохранения. Это необходимо, поскольку управляемые сущности хранятся в структуре Map
, где ключ формируется типом сущности и ее идентификатором, а значение является ссылкой на сущность. По этой причине JPA EntityManager
и Hibernate Сеанс
известны как кэш первого уровня.
При вызове persist
сущность присоединяется только к текущему контексту сохранения , и ВСТАВКА может быть отложена до тех пор, пока не будет вызван сброс
.
Единственным исключением является генератор идентификаторов, который сразу же запускает ВСТАВКУ, поскольку это единственный способ получить идентификатор сущности. По этой причине 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
возвращает идентификатор сущности.
Специфичный для гибернации обновление
метод предназначен для обхода механизма проверки на загрязнение и принудительного обновления объекта во время сброса.
Метод update
запускает SaveOrUpdateEvent
, который обрабатывается DefaultSaveOrUpdateEventListener
Прослушиватель событий гибернации. Следовательно, метод update
эквивалентен методам save
и saveOrUpdate
.
Чтобы увидеть, как работает метод update
, рассмотрим следующий пример, в котором сущность Book
сохраняется в одной транзакции, затем она изменяет ее, пока сущность находится в отключенном состоянии, и запускает обновление 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
Обратите внимание, что ОБНОВЛЕНИЕ
выполняется во время сброса контекста сохранения, непосредственно перед фиксацией, и поэтому сообщение Обновление сущности книги
регистрируется первым.
Используйте @SelectBeforeUpdate, чтобы избежать ненужных обновлений
Теперь ОБНОВЛЕНИЕ всегда будет выполняться, даже если объект не был изменен, находясь в отключенном состоянии. Чтобы предотвратить это, вы можете использовать @SelectBeforeUpdate
Аннотация спящего режима, которая вызовет SELECT
оператор, который извлек загруженное состояние
, которое затем используется механизмом проверки на загрязнение.
Итак, если мы аннотируем сущность 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
Обратите внимание, что на этот раз ОБНОВЛЕНИЕ
не выполняется, так как механизм проверки на наличие ошибок в спящем режиме обнаружил, что объект не был изменен.
Специфичный для гибернации метод saveOrUpdate
является просто псевдонимом для сохранения
и обновления
.
Метод saveOrUpdate
запускает SaveOrUpdateEvent
, который обрабатывается DefaultSaveOrUpdateEventListener
Прослушиватель событий гибернации. Следовательно, метод update
эквивалентен методам save
и saveOrUpdate
.
Теперь вы можете использовать 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"); doInJPA(entityManager -> { Session session = entityManager.unwrap(Session.class); session.saveOrUpdate(_book); });
Одна из проблем, которая может возникнуть с 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 создаст исключение uniqueobjectexception
, поскольку второй EntityManager
уже содержит Книгу
сущность с тем же идентификатором, что и тот, который мы передаем обновление
, и контекст сохранения не может содержать два представления одной и той же сущности.
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)
Чтобы избежать исключения uniqueobjectexception
, вам необходимо использовать метод merge
, предлагаемый JPA EntityManager
и унаследованный Hibernate Сеансом
.
Как объяснено в этой статье , merge
извлекает новый снимок сущности из базы данных, если в контексте сохранения не найдена ссылка на сущность, и копирует состояние отсоединенного объекта, переданного методу merge
.
Метод merge
запускает событие Merge
, которое обрабатывается прослушивателем событий 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 слияние
при копировании состояния отделенной сущности, дополнительный ВЫБОР
может быть проблематичным при выполнении задачи пакетной обработки.
По этой причине вам следует предпочесть использовать update
, когда вы уверены, что к текущему контексту сохранения уже не прикреплена ссылка на сущность и что отсоединенный объект был изменен. Для получения более подробной информации об этой теме ознакомьтесь с этой статьей .
Чтобы сохранить сущность, вы должны использовать метод JPA persist
. Чтобы скопировать состояние обособленной сущности, следует предпочесть объединить
. Метод update
полезен только для задач пакетной обработки. save
и saveOrUpdate
являются просто псевдонимами update
, и вам, вероятно, вообще не следует их использовать.
Некоторые разработчики вызывают сохранить
, даже если объект уже управляется, но это ошибка и вызывает избыточное событие, поскольку для управляемых объектов ОБНОВЛЕНИЕ автоматически обрабатывается во время сброса контекста сохранения.