EntityManager.merge()
dapat menyisipkan objek baru dan memperbarui yang sudah ada.
Mengapa seseorang ingin menggunakan persist()
(yang hanya dapat membuat objek baru)?
EntityManager.merge()
dapat menyisipkan objek baru dan memperbarui yang sudah ada.
Mengapa seseorang ingin menggunakan persist()
(yang hanya dapat membuat objek baru)?
Jawaban:
Apa pun cara akan menambahkan entitas ke PersistenceContext, perbedaannya adalah apa yang Anda lakukan dengan entitas setelahnya.
Persist mengambil instance entitas, menambahkannya ke konteks dan membuat instance itu dikelola (yaitu pembaruan masa depan untuk entitas akan dilacak).
Gabungkan mengembalikan contoh terkelola yang statusnya digabungkan. Itu mengembalikan sesuatu yang ada di PersistenceContext atau menciptakan instance baru dari entitas Anda. Bagaimanapun, itu akan menyalin negara dari entitas yang disediakan, dan mengembalikan salinan yang dikelola. Contoh yang Anda lewati tidak akan dikelola (perubahan apa pun yang Anda lakukan tidak akan menjadi bagian dari transaksi - kecuali jika Anda memanggil penggabungan lagi). Anda dapat melalui penggunaan instance yang dikembalikan (dikelola satu).
Mungkin contoh kode akan membantu.
MyEntity e = new MyEntity();
// scenario 1
// tran starts
em.persist(e);
e.setSomeField(someValue);
// tran ends, and the row for someField is updated in the database
// scenario 2
// tran starts
e = new MyEntity();
em.merge(e);
e.setSomeField(anotherValue);
// tran ends but the row for someField is not updated in the database
// (you made the changes *after* merging)
// scenario 3
// tran starts
e = new MyEntity();
MyEntity e2 = em.merge(e);
e2.setSomeField(anotherValue);
// tran ends and the row for someField is updated
// (the changes were made to e2, not e)
Skenario 1 dan 3 kira-kira setara, tetapi ada beberapa situasi di mana Anda ingin menggunakan Skenario 2.
merge
salinan penuh dari suatu objek sebelum mengelolanya memiliki kinerja yang baik?
@GeneratedId
bisakah saya mendapatkannya di skenario 2?
Bertahan dan bergabung adalah untuk dua tujuan yang berbeda (mereka bukan alternatif sama sekali).
(diedit untuk memperluas informasi perbedaan)
bertahan:
menggabungkan:
bertahan () efisiensi:
tahan () semantik:
Contoh:
{
AnyEntity newEntity;
AnyEntity nonAttachedEntity;
AnyEntity attachedEntity;
// Create a new entity and persist it
newEntity = new AnyEntity();
em.persist(newEntity);
// Save 1 to the database at next flush
newEntity.setValue(1);
// Create a new entity with the same Id than the persisted one.
AnyEntity nonAttachedEntity = new AnyEntity();
nonAttachedEntity.setId(newEntity.getId());
// Save 2 to the database at next flush instead of 1!!!
nonAttachedEntity.setValue(2);
attachedEntity = em.merge(nonAttachedEntity);
// This condition returns true
// merge has found the already attached object (newEntity) and returns it.
if(attachedEntity==newEntity) {
System.out.print("They are the same object!");
}
// Set 3 to value
attachedEntity.setValue(3);
// Really, now both are the same object. Prints 3
System.out.println(newEntity.getValue());
// Modify the un attached object has no effect to the entity manager
// nor to the other objects
nonAttachedEntity.setValue(42);
}
Cara ini hanya ada 1 objek terlampir untuk register apa pun di manajer entitas.
menggabungkan () untuk entitas dengan id adalah sesuatu seperti:
AnyEntity myMerge(AnyEntity entityToSave) {
AnyEntity attached = em.find(AnyEntity.class, entityToSave.getId());
if(attached==null) {
attached = new AnyEntity();
em.persist(attached);
}
BeanUtils.copyProperties(attached, entityToSave);
return attached;
}
Meskipun jika terhubung ke gabungan MySQL () bisa seefisien bertahan () menggunakan panggilan ke INSERT dengan opsi ON DUPLICATE KEY UPDATE, JPA adalah pemrograman tingkat yang sangat tinggi dan Anda tidak dapat menganggap ini akan terjadi di mana-mana.
em.persist(x)
dengan x = em.merge(x)
?
merge()
juga bisa melemparEntityExistsException
RuntimeException
, tetapi tidak disebutkan di Javadoc.
Jika Anda menggunakan generator yang ditetapkan, menggunakan gabungan alih-alih bertahan dapat menyebabkan pernyataan SQL yang berlebihan , sehingga memengaruhi kinerja.
Selain itu, memanggil penggabungan untuk entitas yang dikelola juga merupakan kesalahan karena entitas yang dikelola secara otomatis dikelola oleh Hibernate dan statusnya disinkronkan dengan catatan database dengan mekanisme pemeriksaan kotor setelah membilas Konteks Persistence .
Untuk memahami bagaimana semua ini bekerja, Anda harus terlebih dahulu tahu bahwa Hibernate menggeser pola pikir pengembang dari pernyataan SQL ke transisi status entitas .
Setelah suatu entitas dikelola secara aktif oleh Hibernate, semua perubahan akan secara otomatis disebarkan ke database.
Monitor Hibernate yang saat ini terpasang entitas. Tetapi agar suatu entitas dikelola, ia harus dalam status entitas yang benar.
Untuk memahami transisi status JPA dengan lebih baik, Anda dapat memvisualisasikan diagram berikut:
Atau jika Anda menggunakan API khusus Hibernate:
Seperti diilustrasikan oleh diagram di atas, suatu entitas dapat berada di salah satu dari empat negara berikut:
Baru (sementara)
Objek yang baru dibuat yang belum pernah dikaitkan dengan Hibernate Session
(alias Persistence Context
) dan tidak dipetakan ke baris tabel database apa pun dianggap berada dalam keadaan Baru (Transient).
Untuk bertahan, kita perlu secara eksplisit memanggil EntityManager#persist
metode atau memanfaatkan mekanisme kegigihan transitif.
Gigih (Dikelola)
Entitas persisten telah dikaitkan dengan baris tabel database dan dikelola oleh Konteks Persistence yang sedang berjalan. Setiap perubahan yang dibuat untuk entitas seperti itu akan dideteksi dan disebarkan ke database (selama Sesi flush-time). Dengan Hibernate, kita tidak lagi harus menjalankan pernyataan INSERT / UPDATE / DELETE. Hibernate menggunakan gaya kerja tulis-balik transaksional dan perubahan disinkronkan pada saat paling bertanggung jawab terakhir, selama waktu Session
flush saat ini.
Terpisah
Setelah Konteks Persistensi berjalan saat ini ditutup semua entitas yang sebelumnya dikelola menjadi terpisah. Perubahan yang berurutan tidak akan lagi dilacak dan tidak ada sinkronisasi basis data otomatis yang akan terjadi.
Untuk mengaitkan entitas yang terlepas ke Sesi Hibernasi aktif, Anda dapat memilih salah satu opsi berikut:
Pemasangan kembali
Hibernate (tapi bukan JPA 2.1) mendukung pemasangan kembali melalui metode pembaruan Session #. Sesi Hibernate hanya dapat mengaitkan satu objek Entitas untuk baris database yang diberikan. Ini karena Konteks Persistensi bertindak sebagai cache dalam memori (cache level pertama) dan hanya satu nilai (entitas) yang dikaitkan dengan kunci yang diberikan (tipe entitas dan pengidentifikasi basis data). Suatu entitas dapat disambungkan kembali hanya jika tidak ada objek JVM lainnya (cocok dengan baris database yang sama) yang sudah dikaitkan dengan Sesi Hibernasi saat ini.
Penggabungan
Penggabungan akan menyalin status entitas terpisah (sumber) ke instance entitas terkelola (tujuan). Jika entitas penggabungan tidak memiliki yang setara dalam Sesi saat ini, satu akan diambil dari database. Contoh objek yang dilepaskan akan terus tetap terlepas bahkan setelah operasi gabungan.
Dihapus
Meskipun JPA menuntut agar entitas yang dikelola hanya diizinkan untuk dihapus, Hibernate juga dapat menghapus entitas yang terpisah (tetapi hanya melalui panggilan metode penghapusan Sesi #). Entitas yang dihapus hanya dijadwalkan untuk dihapus dan pernyataan DELETE database aktual akan dieksekusi selama Sesi flush-time.
Saya perhatikan bahwa ketika saya menggunakan em.merge
, saya mendapat SELECT
pernyataan untuk setiap INSERT
, bahkan ketika tidak ada bidang yang dihasilkan JPA untuk saya - bidang kunci utama adalah UUID yang saya tetapkan sendiri. Saya beralih ke em.persist(myEntityObject)
dan mendapat INSERT
pernyataan saat itu.
merge()
. Saya memiliki database PostgreSQL dengan tampilan rumit : tampilan agregat data dari beberapa tabel (tabel memiliki struktur yang identik tetapi nama yang berbeda). Jadi JPA coba lakukan merge()
, tetapi sebenarnya JPA pertama kali dibuat SELECT
(database karena pengaturan tampilan dapat mengembalikan beberapa catatan dengan kunci primer yang sama dari tabel yang berbeda!), Kemudian JPA (Hibernate adalah implementasi) gagal: ada beberapa catatan dengan kunci yang sama ( org.hibernate.HibernateException: More than one row with the given identifier was found
). Dalam kasus saya persist()
membantu saya.
Spesifikasi JPA mengatakan hal berikut tentang persist()
.
Jika X adalah objek yang terpisah,
EntityExistsException
mungkin dilemparkan ketika operasi bertahan dipanggil, atau yangEntityExistsException
lainPersistenceException
mungkin dilemparkan pada waktu siram atau komit.
Jadi menggunakan persist()
akan cocok ketika objek tidak harus menjadi objek yang terpisah. Anda mungkin lebih suka kode dilemparkanPersistenceException
sehingga gagal cepat.
Meskipun spesifikasinya tidak jelas , persist()
mungkin mengatur @GeneratedValue
@Id
untuk objek. merge()
Namun harus memiliki objek dengan yang @Id
sudah dibuat.
merge()
namun harus memiliki objek dengan yang @Id
sudah dibuat . " Setiap kali EntityManager tidak menemukan nilai untuk bidang ID objek, itu tetap (dimasukkan) ke dalam DB.
Beberapa detail lebih lanjut tentang penggabungan yang akan membantu Anda menggunakan penggabungan bertahan:
Mengembalikan contoh yang dikelola selain dari entitas asli adalah bagian penting dari proses penggabungan. Jika instance entitas dengan pengidentifikasi yang sama sudah ada dalam konteks persistensi, penyedia akan menimpa statusnya dengan keadaan entitas yang sedang digabungkan, tetapi versi terkelola yang ada harus dikembalikan ke klien sehingga dapat bekas. Jika penyedia tidak memperbarui instance Karyawan dalam konteks persistensi, setiap referensi ke instance tersebut akan menjadi tidak konsisten dengan keadaan baru yang digabungkan.
Ketika menggabungkan () dipanggil pada entitas baru, itu berperilaku serupa dengan operasi bertahan (). Itu menambahkan entitas ke konteks kegigihan, tetapi alih-alih menambahkan instance entitas asli, ia menciptakan salinan baru dan mengelola instance itu sebagai gantinya. Salinan yang dibuat oleh operasi gabungan () tetap ada seolah-olah metode bertahan () dipanggil di atasnya.
Di hadapan hubungan, operasi gabungan () akan berusaha memperbarui entitas yang dikelola untuk menunjuk ke versi terkelola dari entitas yang direferensikan oleh entitas terpisah. Jika entitas memiliki hubungan dengan objek yang tidak memiliki identitas persisten, hasil dari operasi gabungan tidak ditentukan. Beberapa penyedia mungkin mengizinkan salinan terkelola untuk menunjuk ke objek non-persisten, sedangkan yang lain mungkin langsung melemparkan pengecualian. Operasi penggabungan () dapat opsional mengalir dalam kasus-kasus ini untuk mencegah pengecualian terjadi. Kami akan membahas cascading operasi gabungan () di bagian ini. Jika entitas yang digabungkan menunjuk ke entitas yang dihapus, pengecualian IllegalArgumentException akan dilempar.
Hubungan malas memuat adalah kasus khusus dalam operasi penggabungan. Jika hubungan pemuatan malas tidak dipicu pada suatu entitas sebelum terlepas, hubungan itu akan diabaikan ketika entitas tersebut bergabung. Jika hubungan dipicu saat dikelola dan kemudian disetel ke nol saat entitas dilepaskan, versi entitas terkelola juga akan memiliki hubungan yang dihapus selama penggabungan. "
Semua informasi di atas diambil dari "Pro JPA 2 Mastering the Java ™ Persistence API" oleh Mike Keith dan Merrick Schnicariol. Bab 6. Bagian detasemen dan penggabungan. Buku ini sebenarnya adalah buku kedua yang dikhususkan untuk JPA oleh penulis. Buku baru ini memiliki banyak informasi baru dari yang sebelumnya. Saya benar-benar merekomendasikan untuk membaca buku ini untuk orang-orang yang akan terlibat secara serius dengan JPA. Saya minta maaf karena secara anonim mengirimkan jawaban pertama saya.
Ada beberapa perbedaan lagi di antara merge
dan persist
(saya akan sebutkan lagi yang sudah diposting di sini):
D1. merge
tidak membuat entitas yang lulus dikelola, melainkan mengembalikan contoh lain yang dikelola. persist
di sisi lain akan membuat entitas yang lulus dikelola:
//MERGE: passedEntity remains unmanaged, but newEntity will be managed
Entity newEntity = em.merge(passedEntity);
//PERSIST: passedEntity will be managed after this
em.persist(passedEntity);
D2. Jika Anda menghapus suatu entitas dan kemudian memutuskan untuk bertahan kembali, Anda dapat melakukannya hanya dengan bertahan (), karena merge
akan melempar IllegalArgumentException
.
D3. Jika Anda memutuskan untuk mengurus ID Anda secara manual (misalnya dengan menggunakan UUID), maka suatu merge
operasi akan memicu SELECT
kueri berikutnya untuk mencari entitas yang ada dengan ID itu, sementara persist
mungkin tidak memerlukan kueri tersebut.
D4. Ada beberapa kasus ketika Anda tidak mempercayai kode yang memanggil kode Anda, dan untuk memastikan tidak ada data yang diperbarui, melainkan dimasukkan, Anda harus menggunakan persist
.
Saya mendapatkan pengecualian lazyMemuat pada entitas saya karena saya mencoba mengakses koleksi malas yang ada di sesi.
Apa yang akan saya lakukan adalah dalam permintaan terpisah, mengambil entitas dari sesi dan kemudian mencoba mengakses koleksi di halaman jsp saya yang bermasalah.
Untuk meringankan ini, saya memperbarui entitas yang sama di controller saya dan meneruskannya ke jsp saya, meskipun saya bayangkan ketika saya disimpan kembali dalam sesi itu juga akan dapat diakses SessionScope
dan tidak membuangLazyLoadingException
, modifikasi contoh 2:
Yang berikut ini berhasil bagi saya:
// scenario 2 MY WAY
// tran starts
e = new MyEntity();
e = em.merge(e); // re-assign to the same entity "e"
//access e from jsp and it will work dandy!!
Saya menemukan penjelasan ini dari dokumen Hibernate yang mencerahkan, karena mengandung kasus penggunaan:
Penggunaan dan semantik gabungan () tampaknya membingungkan bagi pengguna baru. Pertama, selama Anda tidak mencoba menggunakan status objek yang dimuat dalam satu manajer entitas di manajer entitas baru lainnya, Anda tidak perlu menggunakan gabungan () sama sekali . Beberapa aplikasi secara keseluruhan tidak akan pernah menggunakan metode ini.
Biasanya menggabungkan () digunakan dalam skenario berikut:
- Aplikasi memuat objek di manajer entitas pertama
- objek diteruskan ke lapisan presentasi
- beberapa modifikasi dilakukan pada objek
- objek dilewatkan kembali ke lapisan logika bisnis
- aplikasi tetap menggunakan modifikasi ini dengan memanggil penggabungan () di manajer entitas kedua
Berikut adalah semantik gabungan yang tepat ():
- jika ada instance yang dikelola dengan pengidentifikasi yang sama saat ini terkait dengan konteks kegigihan, salin keadaan objek yang diberikan ke instance yang dikelola
- jika tidak ada instance yang dikelola saat ini terkait dengan konteks persistence, cobalah memuatnya dari database, atau buat instance yang dikelola baru
- contoh terkelola dikembalikan
- contoh yang diberikan tidak menjadi terkait dengan konteks kegigihan, itu tetap terpisah dan biasanya dibuang
Dari: http://docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html/objectstate.html
Menjelajahi jawaban ada beberapa detail yang hilang tentang `Cascade 'dan pembuatan id. Lihat pertanyaan
Juga, perlu disebutkan bahwa Anda dapat memiliki Cascade
anotasi terpisah untuk digabungkan dan bertahan: Cascade.MERGE
dan Cascade.PERSIST
yang akan diperlakukan sesuai dengan metode yang digunakan.
Speknya adalah teman Anda;)
JPA tidak dapat disangkal merupakan penyederhanaan besar dalam domain aplikasi perusahaan yang dibangun pada platform Java. Sebagai pengembang yang harus mengatasi seluk-beluk kacang entitas lama di J2EE saya melihat dimasukkannya JPA di antara spesifikasi Java EE sebagai lompatan besar ke depan. Namun, saat menggali lebih dalam ke detail JPA saya menemukan hal-hal yang tidak begitu mudah. Dalam artikel ini saya berurusan dengan perbandingan penggabungan dan metode EntityManager yang perilakunya yang tumpang tindih dapat menyebabkan kebingungan tidak hanya untuk pemula. Selanjutnya saya mengusulkan generalisasi yang melihat kedua metode sebagai kasus khusus dari metode yang lebih umum digabungkan.
Entitas yang bertahan lama
Berbeda dengan metode penggabungan, metode bertahan ini cukup mudah dan intuitif. Skenario yang paling umum dari penggunaan metode bertahan dapat diringkas sebagai berikut:
"Sebuah instance yang baru dibuat dari kelas entitas diteruskan ke metode bertahan. Setelah metode ini kembali, entitas dikelola dan direncanakan untuk dimasukkan ke dalam database. Ini dapat terjadi pada atau sebelum transaksi dilakukan atau ketika metode flush dipanggil. Jika entitas merujuk entitas lain melalui hubungan yang ditandai dengan strategi kaskade PERSIST, prosedur ini juga berlaku untuknya. "
Spesifikasi lebih ke rincian, namun mengingatnya tidak penting karena rincian ini mencakup situasi yang kurang lebih eksotis saja.
Menggabungkan entitas
Dibandingkan dengan bertahan, deskripsi perilaku gabungan tidak begitu sederhana. Tidak ada skenario utama, seperti dalam kasus bertahan, dan seorang programmer harus mengingat semua skenario untuk menulis kode yang benar. Tampak bagi saya bahwa desainer JPA ingin memiliki beberapa metode yang perhatian utamanya akan menangani entitas terpisah (sebagai kebalikan dari metode bertahan yang berhubungan dengan entitas yang baru dibuat terutama.) Tugas utama metode penggabungan adalah untuk mentransfer negara dari suatu entitas yang tidak dikelola (diteruskan sebagai argumen) ke mitra yang dikelola dalam konteks persistensi. Namun, tugas ini membagi lebih jauh ke dalam beberapa skenario yang memperburuk kejelasan perilaku keseluruhan metode.
Alih-alih mengulangi paragraf dari spesifikasi JPA saya telah menyiapkan diagram alir yang secara skematis menggambarkan perilaku metode gabungan:
Jadi, kapan saya harus menggunakan bertahan dan kapan bergabung?
bertahan
menggabungkan
Skenario X:
Tabel: Spitter (Satu), Tabel: Spittles (Banyak) (Spittles adalah Pemilik hubungan dengan FK: spitter_id)
Skenario ini menghasilkan penghematan: The Spitter dan kedua Spittles seolah-olah dimiliki oleh Same Spitter.
Spitter spitter=new Spitter();
Spittle spittle3=new Spittle();
spitter.setUsername("George");
spitter.setPassword("test1234");
spittle3.setSpittle("I love java 2");
spittle3.setSpitter(spitter);
dao.addSpittle(spittle3); // <--persist
Spittle spittle=new Spittle();
spittle.setSpittle("I love java");
spittle.setSpitter(spitter);
dao.saveSpittle(spittle); //<-- merge!!
Skenario Y:
Ini akan menyelamatkan Spitter, akan menyelamatkan 2 Spittles Tetapi mereka tidak akan merujuk Spitter yang sama!
Spitter spitter=new Spitter();
Spittle spittle3=new Spittle();
spitter.setUsername("George");
spitter.setPassword("test1234");
spittle3.setSpittle("I love java 2");
spittle3.setSpitter(spitter);
dao.save(spittle3); // <--merge!!
Spittle spittle=new Spittle();
spittle.setSpittle("I love java");
spittle.setSpitter(spitter);
dao.saveSpittle(spittle); //<-- merge!!
Pengamatan lain:
merge()
hanya akan peduli dengan id yang dibuat secara otomatis (diuji pada IDENTITY
dan SEQUENCE
) ketika catatan dengan id tersebut sudah ada di tabel Anda. Dalam hal ini merge()
akan mencoba memperbarui catatan. Namun, jika id tidak ada atau tidak cocok dengan catatan yang ada, merge()
akan sepenuhnya mengabaikannya dan meminta db untuk mengalokasikan yang baru. Ini terkadang merupakan sumber dari banyak bug. Jangan gunakanmerge()
untuk memaksa id untuk catatan baru.
persist()
di sisi lain tidak akan pernah membiarkan Anda memberikan id padanya. Ini akan segera gagal. Dalam kasus saya, ini:
Disebabkan oleh: org.hibernate.PersistentObjectException: entitas terpisah dilewatkan untuk bertahan
hibernate-jpa javadoc memiliki petunjuk:
Lempar : javax.persistence.EntityExistsException - jika entitas sudah ada. (Jika entitas sudah ada, EntityExistsException dapat dilemparkan ketika operasi persisten dipanggil, atau EntityExistsException atau PersistenceException lain dapat dilemparkan pada waktu siram atau komit.)
persist()
tidak akan mengeluh bahwa ia memiliki ID, itu hanya mengeluh ketika sesuatu dengan ID yang sama sudah ada dalam database.
Anda mungkin datang ke sini untuk meminta nasihat tentang kapan harus menggunakan bertahan dan kapan harus menggunakan gabungan . Saya pikir itu tergantung situasinya: seberapa mungkin Anda perlu membuat catatan baru dan seberapa sulit untuk mengambil data yang ada.
Mari kita anggap Anda dapat menggunakan kunci alami / pengidentifikasi.
Data perlu dipertahankan, tetapi sesekali catatan ada dan pembaruan diperlukan. Dalam hal ini Anda bisa mencoba bertahan dan jika itu melempar EntityExistsException, Anda mencarinya dan menggabungkan data:
coba {entitasManager.persist (entitas)}
catch (pengecualian EntityExistsException) {/ * mengambil dan menggabungkan * /}
Data yang bertahan perlu diperbarui, tetapi sesekali tidak ada catatan untuk data tersebut. Dalam hal ini Anda mencarinya, dan lakukan terus jika entitas hilang:
entitas = entitasManager.find (kunci);
if (entitas == null) {entityManager.persist (entitas); }
lain {/ * gabung * /}
Jika Anda tidak memiliki kunci alami / pengidentifikasi, Anda akan memiliki waktu lebih sulit untuk mengetahui apakah entitas itu ada atau tidak, atau bagaimana cara mencarinya.
Gabungan dapat ditangani dengan dua cara, juga:
persist (entitas) harus digunakan dengan entitas yang benar-benar baru, untuk menambahkannya ke DB (jika entitas sudah ada di DB akan ada lemparan EntityExistsException).
penggabungan (entitas) harus digunakan, untuk mengembalikan entitas ke konteks persistensi jika entitas dilepaskan dan diubah.
Mungkin tetap menghasilkan pernyataan INSERT sql dan menggabungkan pernyataan UPDATE sql (tapi saya tidak yakin).