Seperti yang saya jelaskan dalam artikel ini , Anda harus lebih memilih metode JPA sebagian besar waktu, dan updateuntuk tugas-tugas pemrosesan batch.
Entitas JPA atau Hibernate dapat berada di salah satu dari empat negara berikut:
- Transient (Baru)
- Dikelola (Persisten)
- Terpisah
- Dihapus (Dihapus)
Transisi dari satu negara ke yang lain dilakukan melalui metode EntityManager atau Sesi.
Misalnya, JPA EntityManagermenyediakan metode transisi status entitas berikut.

Hibernate Sessionmengimplementasikan semua EntityManagermetode JPA dan menyediakan beberapa metode transisi status entitas tambahan seperti save, saveOrUpdatedan update.

Bertahan
Untuk mengubah status entitas dari Transient (Baru) ke Managed (Persisted), kita dapat menggunakan persistmetode yang ditawarkan oleh JPA EntityManageryang juga diwarisi oleh Hibernate Session.
The persistMetode memicu PersistEventyang ditangani oleh DefaultPersistEventListenerHibernate pendengar acara.
Karena itu, ketika menjalankan uji kasus berikut:
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 menghasilkan pernyataan SQL berikut:
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
)
Perhatikan bahwa idditugaskan sebelum melampirkan Bookentitas ke Konteks Persistence saat ini. Ini diperlukan karena entitas yang dikelola disimpan dalam Mapstruktur di mana kunci dibentuk oleh jenis entitas dan pengenalnya dan nilainya adalah referensi entitas. Ini adalah alasan mengapa JPA EntityManagerdan Hibernate Sessiondikenal sebagai Cache Tingkat Pertama.
Saat memanggil persist, entitas hanya dilampirkan ke Konteks Persistence yang sedang berjalan, dan INSERT dapat ditunda hingga flushdipanggil.
Satu-satunya pengecualian adalah generator IDENTITY yang memicu INSERT segera karena itulah satu-satunya cara ia bisa mendapatkan pengenal entitas. Karena alasan ini, Hibernate tidak dapat memasukkan batch untuk entitas menggunakan generator IDENTITY. Untuk detail lebih lanjut tentang topik ini, lihat artikel ini .
Menyimpan
Khusus Hibernate saveMetode mendahului JPA dan sudah tersedia sejak awal proyek Hibernate.
The saveMetode memicu SaveOrUpdateEventyang ditangani oleh DefaultSaveOrUpdateEventListenerHibernate pendengar acara. Oleh karena itu, savemetode ini setara dengan updatedansaveOrUpdate metode .
Untuk melihat bagaimana savemetode ini bekerja, pertimbangkan uji kasus berikut:
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
);
});
Saat menjalankan test case di atas, Hibernate menghasilkan pernyataan SQL berikut:
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
)
Seperti yang Anda lihat, hasilnya identik dengan persistpemanggilan metode. Namun, tidak seperti persist,save metode mengembalikan pengenal entitas.
Untuk lebih jelasnya, lihat artikel ini .
Memperbarui
updateMetode khusus Hibernate dimaksudkan untuk mem-bypass mekanisme pemeriksaan kotor dan memaksa pembaruan entitas pada waktu flush.
The updateMetode memicu SaveOrUpdateEventyang ditangani oleh DefaultSaveOrUpdateEventListenerHibernate pendengar acara. Oleh karena itu, updatemetode ini setara dengan metode savedansaveOrUpdate .
Untuk melihat bagaimana updatemetode ini bekerja, perhatikan contoh berikut ini yang bertahan suatu Bookentitas dalam satu transaksi, kemudian memodifikasinya saat entitas dalam keadaan terlepas, dan itu memaksa SQL UPDATE menggunakan updatepemanggilan metode.
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");
});
Saat menjalankan uji kasus di atas, Hibernate menghasilkan pernyataan SQL berikut:
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
Perhatikan bahwa UPDATEdieksekusi selama flush Konteks Persistence, tepat sebelum komit, dan itulah sebabnyaUpdating the Book entity pesan dicatat terlebih dahulu.
Menggunakan @SelectBeforeUpdate untuk menghindari pembaruan yang tidak perlu
Sekarang, UPDATE selalu akan dieksekusi bahkan jika entitas tidak berubah saat dalam keadaan terpisah. Untuk mencegah hal ini, Anda dapat menggunakan @SelectBeforeUpdateanotasi Hibernate yang akan memicu SELECTpernyataan yang diambilloaded state yang kemudian digunakan oleh mekanisme pemeriksaan kotor.
Jadi, jika kita membubuhi keterangan Bookentitas dengan @SelectBeforeUpdateanotasi:
@Entity(name = "Book")
@Table(name = "book")
@SelectBeforeUpdate
public class Book {
//Code omitted for brevity
}
Dan jalankan test case berikut ini:
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 menjalankan pernyataan SQL berikut:
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
Perhatikan bahwa, kali ini, tidak ada UPDATE dieksekusi karena mekanisme pengecekan kotor Hibernate mendeteksi bahwa entitas tidak dimodifikasi.
SimpanOrUpdate
saveOrUpdateMetode khusus Hibernate hanyalah alias untuk savedan update.
The saveOrUpdateMetode memicu SaveOrUpdateEventyang ditangani oleh DefaultSaveOrUpdateEventListenerHibernate pendengar acara. Oleh karena itu, updatemetode ini setara dengan metode savedansaveOrUpdate .
Sekarang, Anda dapat menggunakan saveOrUpdatesaat Anda ingin bertahan suatu entitas atau memaksa UPDATEseperti yang diilustrasikan oleh contoh berikut.
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);
});
Waspadalah terhadap NonUniqueObjectException
Salah satu masalah yang dapat terjadi dengan save,, updatedan saveOrUpdateadalah jika Konteks Persistensi sudah berisi referensi entitas dengan id yang sama dan dari jenis yang sama seperti dalam contoh berikut:
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
);
}
Sekarang, ketika menjalankan test case di atas, Hibernate akan melempar NonUniqueObjectExceptionkarena yang kedua EntityManagersudah berisi Bookentitas dengan pengidentifikasi yang sama seperti yang kita lewati update, dan Persistence Context tidak dapat menampung dua representasi dari entitas yang sama.
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)
Menggabungkan
Untuk menghindarinya NonUniqueObjectException, Anda perlu menggunakan mergemetode yang ditawarkan oleh JPA EntityManagerdan diwarisi oleh Hibernate Sessionjuga.
Seperti yang dijelaskan dalam artikel ini , mergemengambil snapshot entitas baru dari database jika tidak ada referensi entitas yang ditemukan dalam Konteks Persistence, dan salinan keadaan entitas terpisah dilewatkan ke mergemetode.
The mergeMetode memicu MergeEventyang ditangani oleh DefaultMergeEventListenerHibernate pendengar acara.
Untuk melihat bagaimana mergemetode ini bekerja, perhatikan contoh berikut ini yang bertahan suatu Bookentitas dalam satu transaksi, kemudian memodifikasinya saat entitas berada dalam keadaan terlepas, dan meneruskan entitas yang terlepas ke mergedalam Konteks Persistence selanjutnya.
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);
});
Saat menjalankan test case di atas, Hibernate menjalankan pernyataan SQL berikut:
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
Perhatikan bahwa referensi entitas yang dikembalikan oleh mergeberbeda dengan yang kita lewati untuk mergemetode ini.
Sekarang, meskipun Anda sebaiknya memilih menggunakan JPA mergesaat menyalin status entitas terpisah, ekstra SELECTbisa bermasalah saat menjalankan tugas pemrosesan batch.
Untuk alasan ini, Anda sebaiknya menggunakan update ketika Anda yakin bahwa tidak ada referensi entitas yang sudah dilampirkan ke Konteks Persistence yang sedang berjalan dan bahwa entitas yang terlepas telah dimodifikasi.
Untuk detail lebih lanjut tentang topik ini, lihat artikel ini .
Kesimpulan
Untuk bertahan suatu entitas, Anda harus menggunakan persistmetode JPA . Untuk menyalin status entitas terpisah, mergeharus lebih disukai. The updateMetode ini berguna untuk tugas-tugas pemrosesan batch saja. The savedan saveOrUpdatehanya alias untuk updatedan Anda tidak boleh menggunakannya sama sekali.
Beberapa pengembang memanggil savebahkan ketika entitas sudah dikelola, tetapi ini adalah kesalahan dan memicu peristiwa yang berlebihan karena, untuk entitas yang dikelola, UPDATE secara otomatis ditangani pada waktu flush konteks Persistence.
Untuk lebih jelasnya, lihat artikel ini .