JPA: penghapusan banyak-ke-satu dan bertingkat searah

98

Katakanlah saya memiliki hubungan searah @ManyToOne seperti berikut:

@Entity
public class Parent implements Serializable {

    @Id
    @GeneratedValue
    private long id;
}

@Entity
public class Child implements Serializable {

    @Id
    @GeneratedValue
    private long id;

    @ManyToOne
    @JoinColumn
    private Parent parent;  
}

Jika saya memiliki orang tua P dan anak C 1 ... C n mereferensikan kembali ke P, apakah ada cara yang bersih dan cantik di JPA untuk secara otomatis menghapus anak C 1 ... C n saat P dihapus (yaitu entityManager.remove(P))?

Yang saya cari adalah fungsi yang mirip dengan ON DELETE CASCADESQL.

pelaku
sumber
1
Sekalipun hanya 'Anak' yang memiliki referensi ke 'Induk' (dengan cara demikian, referensi tersebut searah) apakah bermasalah bagi Anda untuk menambahkan Daftar 'Anak' dengan pemetaan '@OneToMany' dan atribut 'Cascade = ALL' ke orang tua'? Saya berasumsi bahwa JPA harus menyelesaikan itu meski sulit hanya 1 pihak yang memegang referensi.
kvDennis
1
@kvDennis, ada kasus di mana Anda tidak ingin menyandingkan banyak sisi ke satu sisi. Misalnya dalam pengaturan seperti ACL di mana izin keamanan transparan "add-on"
Bachi

Jawaban:

73

Hubungan di JPA selalu searah, kecuali jika Anda mengaitkan induk dengan anaknya di kedua arah. Operasi Cascading REMOVE dari induk ke anak akan membutuhkan relasi dari induk ke anak (bukan hanya sebaliknya).

Oleh karena itu, Anda harus melakukan ini:

  • Baik, ubah @ManyToOnehubungan searah menjadi dua arah @ManyToOne, atau searah @OneToMany. Anda kemudian dapat melakukan operasi Cascade REMOVE sehingga EntityManager.removeakan menghapus induk dan anak. Anda juga dapat menetapkan orphanRemovalsebagai benar, untuk menghapus anak yatim piatu saat entitas anak dalam koleksi induk disetel ke nol, yaitu menghapus anak jika tidak ada dalam koleksi induk mana pun.
  • Atau, tentukan batasan kunci asing di tabel anak sebagai ON DELETE CASCADE. Anda harus memanggil EntityManager.clear()setelah memanggil EntityManager.remove(parent)karena konteks persistensi perlu di-refresh - entitas turunan tidak seharusnya ada dalam konteks persistensi setelah dihapus dalam database.
Vineet Reynolds
sumber
7
apakah ada cara untuk melakukan No2 dengan anotasi JPA?
pengguna2573153
3
Bagaimana cara melakukan No2 dengan pemetaan xml Hibernate?
arg20
92

Jika Anda menggunakan hibernasi sebagai penyedia JPA, Anda dapat menggunakan anotasi @OnDelete. Anotasi ini akan menambah relasi pemicu ON DELETE CASCADE , yang mendelegasikan penghapusan anak ke database.

Contoh:

public class Parent {

        @Id
        private long id;

}


public class Child {

        @Id
        private long id;

        @ManyToOne
        @OnDelete(action = OnDeleteAction.CASCADE)
        private Parent parent;
}

Dengan solusi ini, hubungan searah dari anak ke induk sudah cukup untuk secara otomatis menghapus semua anak. Solusi ini tidak membutuhkan pendengar, dll. Juga kueri seperti DELETE FROM Parent WHERE id = 1 akan menghapus anak-anak.

Thomas Hunziker
sumber
4
Saya tidak dapat membuatnya berfungsi dengan cara ini, apakah ada versi hibernasi tertentu atau contoh lain yang lebih detail seperti ini?
Mardari
3
Sulit untuk mengatakan mengapa itu tidak berhasil untuk Anda. Agar ini berfungsi, Anda mungkin perlu membuat ulang skema atau Anda harus menambahkan penghapusan kaskade secara manual. Anotasi @OnDelete tampaknya ada untuk sementara waktu sehingga saya tidak akan menduga bahwa versinya adalah masalah.
Thomas Hunziker
10
Terima kasih atas jawabannya. Catatan cepat: pemicu kaskade database hanya akan dibuat jika Anda telah mengaktifkan pembuatan DDL melalui hibernasi. Jika tidak, Anda harus menambahkannya dengan cara lain (mis. Liquibase) untuk memungkinkan kueri ad hoc dijalankan secara langsung terhadap DB seperti 'DELETE FROM Parent WHERE id = 1' melakukan penghapusan kaskade.
mjj1409
1
ini tidak berfungsi ketika asosiasi @OneToOneAda ide bagaimana mengatasinya @OneToOne?
stakowerflol
1
@ThomasHunziker ini tidak akan bekerja untuk orphanRemoval kan?
oxyt
13

Buat hubungan dua arah, seperti ini:

@Entity
public class Parent implements Serializable {

    @Id
    @GeneratedValue
    private long id;

    @OneToMany(mappedBy = "parent", cascade = CascadeType.REMOVE)
    private Set<Child> children;
}
tekumara
sumber
8
jawaban buruk, hubungan dua arah buruk di JPA karena beroperasi pada set anak-anak yang besar membutuhkan banyak waktu
Enerccio
1
Adakah bukti bahwa hubungan dua arah lambat?
shalama
@enerccio Bagaimana jika hubungan dua arah adalah satu-ke-satu? Juga, tolong tunjukkan artikel yang menyatakan hubungan dua arah lambat? lambat dalam apa? mengambil? menghapus? memperbarui?
saran3h
@ saran3h setiap operasi (tambah, hapus) akan memuat semua anak, sehingga beban data besar yang bisa tidak berguna (seperti menambahkan nilai tidak memerlukan memuat semua anak dari database yang persis seperti pemetaan ini).
Enerccio
@Enerccio Saya rasa semua orang menggunakan lazy loading saat bergabung. Jadi bagaimana ini masih menjadi masalah kinerja?
saran3h
1

Saya telah melihat di @ManytoOne searah, hapus tidak berfungsi seperti yang diharapkan. Ketika orang tua dihapus, idealnya anak juga harus dihapus, tetapi hanya orang tua yang dihapus dan anak TIDAK dihapus dan dibiarkan sebagai yatim piatu

Teknologi yang digunakan adalah Spring Boot / Spring Data JPA / Hibernate

Sprint Boot: 2.1.2. LEPAS

Spring Data JPA / Hibernate digunakan untuk menghapus baris .eg

parentRepository.delete(parent)

ParentRepository memperluas repositori CRUD standar seperti yang ditunjukkan di bawah ini ParentRepository extends CrudRepository<T, ID>

Berikut adalah kelas entitas saya

@Entity(name = child”)
public class Child  {

    @Id
    @GeneratedValue
    private long id;

    @ManyToOne( fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = parent_id", nullable = false)
    @OnDelete(action = OnDeleteAction.CASCADE)
    private Parent parent;
}

@Entity(name = parent”)
public class Parent {

    @Id
    @GeneratedValue
    private long id;

    @Column(nullable = false, length = 50)
    private String firstName;


}
ranjesh
sumber
Saya menemukan solusi mengapa delete tidak berfungsi. ternyata hibernate TIDAK menggunakan mesin mysql -INNODB, Anda memerlukan mesin INNODB untuk mysql untuk menghasilkan batasan kunci asing. Menggunakan properti berikut di application.properties, membuat boot musim semi / hibernasi untuk menggunakan mesin mysql INNODB. Jadi batasan kunci asing berfungsi dan karenanya juga menghapus cascade
ranjesh
Properti terlewat digunakan dalam komentar sebelumnya. berikut adalah properti musim semi yang digunakanspring.jpa.hibernate.use-new-id-generator-mappings=true spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
ranjesh
FYI, Anda salah "dalam kode. Lihatname= "parent"
Alexander
1

Gunakan cara ini untuk menghapus hanya satu sisi

    @ManyToOne(cascade=CascadeType.PERSIST, fetch = FetchType.LAZY)
//  @JoinColumn(name = "qid")
    @JoinColumn(name = "qid", referencedColumnName = "qid", foreignKey = @ForeignKey(name = "qid"), nullable = false)
    // @JsonIgnore
    @JsonBackReference
    private QueueGroup queueGroup;
Shubham
sumber
-1

@Cascade (org.hibate.annotations.CascadeType.DELETEORPHAN)

Anotasi yang diberikan berhasil untuk saya. Bisa dicoba

Sebagai contoh :-

     public class Parent{
            @Id
            @GeneratedValue(strategy=GenerationType.AUTO)
            @Column(name="cct_id")
            private Integer cct_id;
            @OneToMany(cascade=CascadeType.REMOVE, fetch=FetchType.EAGER,mappedBy="clinicalCareTeam", orphanRemoval=true)
            @Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
            private List<Child> childs;
        }
            public class Child{
            @ManyToOne(fetch=FetchType.EAGER)
            @JoinColumn(name="cct_id")
            private Parent parent;
    }
Swarit Agarwal
sumber