Saya memiliki model objek JPA-persisten yang berisi hubungan banyak-ke-satu: Account
memiliki banyak Transactions
. A Transaction
memiliki satu Account
.
Berikut cuplikan kode:
@Entity
public class Transaction {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
private Account fromAccount;
....
@Entity
public class Account {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@OneToMany(cascade = {CascadeType.ALL},fetch= FetchType.EAGER, mappedBy = "fromAccount")
private Set<Transaction> transactions;
Saya dapat membuat Account
objek, menambahkan transaksi, dan bertahan Account
objek dengan benar. Tetapi, ketika saya membuat transaksi, menggunakan Akun yang sudah ada yang sudah ada , dan bertahan dalam Transaksi , saya mendapatkan pengecualian:
Disebabkan oleh: org.hibernate.PersistentObjectException: entitas terpisah dilewatkan untuk bertahan: com.paulsanwald.Akun di org.hibernate.event.internal.DefaultPersistEventListener.onPersist (DefaultPersistEventListener.java:141)
Jadi, saya bisa bertahan Account
yang mengandung transaksi, tetapi bukan Transaksi yang memiliki Account
. Saya pikir ini karena Account
mungkin tidak dilampirkan, tetapi kode ini masih memberi saya pengecualian yang sama:
if (account.getId()!=null) {
account = entityManager.merge(account);
}
Transaction transaction = new Transaction(account,"other stuff");
// the below fails with a "detached entity" message. why?
entityManager.persist(transaction);
Bagaimana saya bisa menyimpan dengan benar Transaction
, yang terkait dengan objek yang sudah ada Account
?
Jawaban:
Ini adalah masalah konsistensi dua arah yang khas. Ini dibahas dengan baik dalam tautan ini dan juga tautan ini.
Sesuai dengan artikel di 2 tautan sebelumnya, Anda perlu memperbaiki setter Anda di kedua sisi hubungan dua arah. Contoh penyetel untuk sisi Satu ada di tautan ini.
Contoh setter untuk sisi Banyak ada di tautan ini.
Setelah Anda memperbaiki setter Anda, Anda ingin mendeklarasikan tipe akses Entity menjadi "Properti". Praktik terbaik untuk menyatakan jenis akses "Properti" adalah memindahkan SEMUA anotasi dari properti anggota ke getter yang sesuai. Kata hati-hati adalah untuk tidak mencampur jenis akses "Field" dan "Property" dalam kelas entitas jika perilaku tidak ditentukan oleh spesifikasi JSR-317.
sumber
detached entity passed to persist
Mengapa meningkatkan konsistensi membuatnya berfungsi? Ok, konsistensi telah diperbaiki tetapi objek masih terpisah.Solusinya sederhana, cukup gunakan
CascadeType.MERGE
bukanCascadeType.PERSIST
atauCascadeType.ALL
.Saya memiliki masalah yang sama dan
CascadeType.MERGE
telah bekerja untuk saya.Saya harap Anda diurutkan.
sumber
Hapus cascading dari entitas anak
Transaction
, seharusnya hanya:(
FetchType.EAGER
dapat dihapus dan itu adalah default untuk@ManyToOne
)Itu saja!
Mengapa? Dengan mengatakan "cascade ALL" pada entitas anak,
Transaction
Anda mengharuskan setiap operasi DB disebarkan ke entitas indukAccount
. Jika Anda melakukannyapersist(transaction)
,persist(account)
akan dipanggil juga.Tetapi hanya entitas transien (baru) yang dapat diteruskan ke
persist
(Transaction
dalam hal ini). Yang terlepas (atau keadaan tidak transien lainnya) mungkin tidak (Account
dalam hal ini, karena sudah dalam DB).Oleh karena itu Anda mendapatkan pengecualian "entitas terpisah yang dilewati untuk bertahan" . The
Account
entitas yang dimaksud! BukanTransaction
kau yang meneleponpersist
.Anda biasanya tidak ingin menyebarkan dari anak ke orang tua. Sayangnya ada banyak contoh kode dalam buku (bahkan yang bagus) dan melalui internet, yang melakukan hal itu. Saya tidak tahu, mengapa ... Mungkin kadang-kadang hanya disalin berulang tanpa banyak berpikir ...
Coba tebak apa yang terjadi jika Anda menyebut
remove(transaction)
masih memiliki "cascade ALL" di @ManyToOne itu? Theaccount
(btw, dengan semua transaksi lain!) Akan dihapus dari DB juga. Tapi itu bukan niatmu, kan?sumber
Menggunakan penggabungan berisiko dan rumit, jadi ini solusi yang kotor dalam kasus Anda. Anda harus ingat setidaknya bahwa ketika Anda melewatkan objek entitas untuk digabung, itu berhenti melekat pada transaksi dan sebagai gantinya, entitas yang sekarang dilampirkan dikembalikan. Ini berarti bahwa jika ada yang memiliki objek entitas lama masih dalam kepemilikan mereka, perubahan itu diabaikan secara diam-diam dan dibuang pada komit.
Anda tidak menunjukkan kode lengkap di sini, jadi saya tidak dapat memeriksa ulang pola transaksi Anda. Salah satu cara untuk sampai ke situasi seperti ini adalah jika Anda tidak memiliki transaksi aktif ketika menjalankan penggabungan dan bertahan. Dalam hal ini penyedia persistensi diharapkan untuk membuka transaksi baru untuk setiap operasi JPA yang Anda lakukan dan segera melakukan dan menutupnya sebelum panggilan kembali. Jika demikian, gabungan akan dijalankan dalam transaksi pertama dan kemudian setelah metode gabungan kembali, transaksi diselesaikan dan ditutup dan entitas yang dikembalikan sekarang terlepas. Bertahan di bawahnya kemudian akan membuka transaksi kedua, dan mencoba merujuk ke entitas yang terlepas, memberikan pengecualian. Selalu bungkus kode Anda di dalam transaksi kecuali Anda tahu betul apa yang Anda lakukan.
Menggunakan transaksi yang dikelola kontainer, akan terlihat seperti ini. Perhatikan: ini mengasumsikan metode ini ada di dalam sesi kacang dan dipanggil melalui antarmuka Lokal atau Remote.
sumber
@Transaction(readonly=false)
di lapisan layanan., Saya masih mendapatkan masalah yang sama,Mungkin dalam kasus ini Anda mendapatkan
account
objek Anda menggunakan logika gabungan, danpersist
digunakan untuk bertahan objek baru dan itu akan mengeluh jika hierarki memiliki objek yang sudah ada. Anda harus menggunakansaveOrUpdate
dalam kasus seperti itu, bukanpersist
.sumber
merge
padatransaction
objek yang Anda mendapatkan kesalahan yang sama?transaction
itu tidak bertahan? Bagaimana Anda memeriksanya. Perhatikan bahwamerge
mengembalikan referensi ke objek tetap.Jangan meneruskan id (pk) untuk bertahan metode atau mencoba metode save () bukannya bertahan ().
sumber
TestEntityManager.persistAndFlush()
untuk membuat instance dikelola dan persisten kemudian menyinkronkan konteks persistensi ke database yang mendasarinya. Mengembalikan entitas sumber asliUntuk memperbaiki masalah, Anda harus mengikuti langkah-langkah ini:
1. Menghapus cascading asosiasi anak
Jadi, Anda harus menghapus
@CascadeType.ALL
dari@ManyToOne
asosiasi. Entitas anak tidak boleh mengalir ke asosiasi orang tua. Hanya entitas induk yang harus mengalir ke entitas anak.Perhatikan bahwa saya mengatur
fetch
atributnyaFetchType.LAZY
karena ingin mengambil sangat buruk untuk kinerja .2. Atur kedua sisi asosiasi
Setiap kali Anda memiliki asosiasi dua arah, Anda perlu menyinkronkan kedua sisi menggunakan
addChild
danremoveChild
metode dalam entitas induk:Untuk detail lebih lanjut tentang mengapa penting untuk menyinkronkan kedua ujung dari asosiasi dua arah, periksa artikel ini .
sumber
Dalam definisi entitas Anda, Anda tidak menentukan @JoinColumn untuk
Account
bergabung dengan aTransaction
. Anda akan menginginkan sesuatu seperti ini:EDIT: Ya, saya kira itu akan berguna jika Anda menggunakan
@Table
anotasi di kelas Anda. Heh. :)sumber
Bahkan jika anotasi Anda dinyatakan dengan benar untuk mengelola hubungan satu-ke-banyak dengan benar, Anda masih dapat menemukan pengecualian yang tepat ini. Saat menambahkan objek anak baru
Transaction
,, ke model data terlampir Anda harus mengelola nilai kunci utama - kecuali Anda tidak seharusnya melakukannya . Jika Anda memberikan nilai kunci utama untuk entitas anak yang dinyatakan sebagai berikut sebelum memanggilpersist(T)
, Anda akan menemukan pengecualian ini.Dalam hal ini, anotasi menyatakan bahwa basis data akan mengelola pembuatan nilai kunci primer entitas saat penyisipan. Memberikannya sendiri (seperti melalui setter Id) menyebabkan pengecualian ini.
Atau, tetapi secara efektif sama, deklarasi anotasi ini menghasilkan pengecualian yang sama:
Jadi, jangan tetapkan
id
nilai dalam kode aplikasi Anda ketika sudah dikelola.sumber
Jika tidak ada yang membantu dan Anda masih mendapatkan pengecualian ini, tinjau
equals()
metode - dan jangan masukkan koleksi anak di dalamnya. Terutama jika Anda memiliki struktur koleksi tertanam yang dalam (mis. A mengandung B, B berisi C, dll.).Dalam contoh
Account -> Transactions
:Dalam contoh di atas hapus transaksi dari
equals()
cek. Ini karena hibernate akan menyiratkan bahwa Anda tidak mencoba memperbarui objek lama, tetapi Anda meneruskan objek baru untuk bertahan, setiap kali Anda mengubah elemen pada koleksi anak.Tentu saja solusi ini tidak cocok untuk semua aplikasi dan Anda harus hati-hati merancang apa yang ingin Anda sertakan dalam
equals
danhashCode
metode.sumber
Mungkin itu adalah bug OpenJPA, Ketika kembalikan itu mengatur ulang bidang @Version, tetapi pcVersionInit tetap benar. Saya memiliki AbstraceEntity yang menyatakan bidang @Version. Saya bisa mengatasinya dengan mereset bidang pcVersionInit. Tapi itu bukan ide yang bagus. Saya pikir itu tidak berfungsi ketika memiliki kaskade bertahan.
sumber
Anda perlu mengatur Transaksi untuk setiap Akun.
Atau cukup colud (jika sesuai) untuk mengatur id ke nol di banyak sisi.
sumber
sumber
Jawaban saya berdasarkan Data Spring JPA: Saya hanya menambahkan
@Transactional
anotasi ke metode luar saya.Mengapa ini berhasil?
Entitas anak langsung terlepas karena tidak ada konteks sesi Hibernate aktif. Menyediakan transaksi Pegas (Data JPA) memastikan Sesi Hibernasi hadir.
Referensi:
https://vladmihalcea.com/a-beginners-guide-to-jpa-hibernate-entity-state-transitions/
sumber
Dalam kasus saya, saya melakukan transaksi ketika metode bertahan digunakan. Pada perubahan bertahan untuk menyimpan metode, itu terselesaikan.
sumber
Jika solusi di atas tidak berfungsi hanya satu kali komentar metode pengambil dan penyetel kelas entitas dan tidak menetapkan nilai id. (Kunci utama) Maka ini akan berhasil.
sumber
@OneToMany (mappedBy = "xxxx", cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE}) bekerja untuk saya.
sumber
Diselesaikan dengan menyimpan objek dependen sebelum yang berikutnya.
Ini terjadi pada saya karena saya tidak mengatur Id (yang tidak dihasilkan secara otomatis). dan mencoba menyimpan dengan relasi @ManytoOne
sumber