Seperti yang saya jelaskan dalam artikel ini , Anda harus lebih memilih metode JPA sebagian besar waktu, dan update
untuk 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 EntityManager
menyediakan metode transisi status entitas berikut.
Hibernate Session
mengimplementasikan semua EntityManager
metode JPA dan menyediakan beberapa metode transisi status entitas tambahan seperti save
, saveOrUpdate
dan update
.
Bertahan
Untuk mengubah status entitas dari Transient (Baru) ke Managed (Persisted), kita dapat menggunakan persist
metode yang ditawarkan oleh JPA EntityManager
yang juga diwarisi oleh Hibernate Session
.
The persist
Metode memicu PersistEvent
yang ditangani oleh DefaultPersistEventListener
Hibernate 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 id
ditugaskan sebelum melampirkan Book
entitas ke Konteks Persistence saat ini. Ini diperlukan karena entitas yang dikelola disimpan dalam Map
struktur di mana kunci dibentuk oleh jenis entitas dan pengenalnya dan nilainya adalah referensi entitas. Ini adalah alasan mengapa JPA EntityManager
dan Hibernate Session
dikenal sebagai Cache Tingkat Pertama.
Saat memanggil persist
, entitas hanya dilampirkan ke Konteks Persistence yang sedang berjalan, dan INSERT dapat ditunda hingga flush
dipanggil.
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 save
Metode mendahului JPA dan sudah tersedia sejak awal proyek Hibernate.
The save
Metode memicu SaveOrUpdateEvent
yang ditangani oleh DefaultSaveOrUpdateEventListener
Hibernate pendengar acara. Oleh karena itu, save
metode ini setara dengan update
dansaveOrUpdate
metode .
Untuk melihat bagaimana save
metode 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 persist
pemanggilan metode. Namun, tidak seperti persist
,save
metode mengembalikan pengenal entitas.
Untuk lebih jelasnya, lihat artikel ini .
Memperbarui
update
Metode khusus Hibernate dimaksudkan untuk mem-bypass mekanisme pemeriksaan kotor dan memaksa pembaruan entitas pada waktu flush.
The update
Metode memicu SaveOrUpdateEvent
yang ditangani oleh DefaultSaveOrUpdateEventListener
Hibernate pendengar acara. Oleh karena itu, update
metode ini setara dengan metode save
dansaveOrUpdate
.
Untuk melihat bagaimana update
metode ini bekerja, perhatikan contoh berikut ini yang bertahan suatu Book
entitas dalam satu transaksi, kemudian memodifikasinya saat entitas dalam keadaan terlepas, dan itu memaksa SQL UPDATE menggunakan update
pemanggilan 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 UPDATE
dieksekusi 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 @SelectBeforeUpdate
anotasi Hibernate yang akan memicu SELECT
pernyataan yang diambilloaded state
yang kemudian digunakan oleh mekanisme pemeriksaan kotor.
Jadi, jika kita membubuhi keterangan Book
entitas dengan @SelectBeforeUpdate
anotasi:
@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
saveOrUpdate
Metode khusus Hibernate hanyalah alias untuk save
dan update
.
The saveOrUpdate
Metode memicu SaveOrUpdateEvent
yang ditangani oleh DefaultSaveOrUpdateEventListener
Hibernate pendengar acara. Oleh karena itu, update
metode ini setara dengan metode save
dansaveOrUpdate
.
Sekarang, Anda dapat menggunakan saveOrUpdate
saat Anda ingin bertahan suatu entitas atau memaksa UPDATE
seperti 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
,, update
dan saveOrUpdate
adalah 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 NonUniqueObjectException
karena yang kedua EntityManager
sudah berisi Book
entitas 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 merge
metode yang ditawarkan oleh JPA EntityManager
dan diwarisi oleh Hibernate Session
juga.
Seperti yang dijelaskan dalam artikel ini , merge
mengambil snapshot entitas baru dari database jika tidak ada referensi entitas yang ditemukan dalam Konteks Persistence, dan salinan keadaan entitas terpisah dilewatkan ke merge
metode.
The merge
Metode memicu MergeEvent
yang ditangani oleh DefaultMergeEventListener
Hibernate pendengar acara.
Untuk melihat bagaimana merge
metode ini bekerja, perhatikan contoh berikut ini yang bertahan suatu Book
entitas dalam satu transaksi, kemudian memodifikasinya saat entitas berada dalam keadaan terlepas, dan meneruskan entitas yang terlepas ke merge
dalam 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 merge
berbeda dengan yang kita lewati untuk merge
metode ini.
Sekarang, meskipun Anda sebaiknya memilih menggunakan JPA merge
saat menyalin status entitas terpisah, ekstra SELECT
bisa 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 persist
metode JPA . Untuk menyalin status entitas terpisah, merge
harus lebih disukai. The update
Metode ini berguna untuk tugas-tugas pemrosesan batch saja. The save
dan saveOrUpdate
hanya alias untuk update
dan Anda tidak boleh menggunakannya sama sekali.
Beberapa pengembang memanggil save
bahkan 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 .