Apa perbedaan antara @JoinColumn dan mappedBy saat menggunakan asosiasi JPA @OneToMany

516

Apa perbedaan antara:

@Entity
public class Company {

    @OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY)
    @JoinColumn(name = "companyIdRef", referencedColumnName = "companyId")
    private List<Branch> branches;
    ...
}

dan

@Entity
public class Company {

    @OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY, mappedBy = "companyIdRef")
    private List<Branch> branches;
    ...
}
Mykhaylo Adamovych
sumber
1
Lihat juga Apa sisi kepemilikan dalam pertanyaan pemetaan ORM untuk penjelasan yang sangat baik tentang masalah yang terlibat.
dirkt

Jawaban:

545

Anotasi @JoinColumnmenunjukkan bahwa entitas ini adalah pemilik hubungan (yaitu: tabel yang sesuai memiliki kolom dengan kunci asing ke tabel yang direferensikan), sedangkan atribut mappedBymenunjukkan bahwa entitas di sisi ini adalah kebalikan dari hubungan, dan pemiliknya berada di entitas "lain". Ini juga berarti bahwa Anda dapat mengakses tabel lain dari kelas yang telah Anda anotasi dengan "mappedBy" (hubungan dua arah penuh).

Khususnya, untuk kode dalam pertanyaan, anotasi yang benar akan terlihat seperti ini:

@Entity
public class Company {
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "company")
    private List<Branch> branches;
}

@Entity
public class Branch {
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "companyId")
    private Company company;
}
Óscar López
sumber
3
dalam kedua kasus Cabang memiliki bidang dengan id Perusahaan.
Mykhaylo Adamovych
3
Tabel Perusahaan tidak memiliki kolom dengan kunci asing ke tabel yang direferensikan - Cabang memiliki referensi untuk Perusahaan .. mengapa Anda mengatakan "tabel terkait memiliki kolom dengan kunci asing ke tabel referensi"? Bisakah Anda jelaskan lebih banyak pls.
Mykhaylo Adamovych
13
@MykhayloAdamovych Saya memperbarui jawaban saya dengan kode sampel. Perhatikan bahwa itu kesalahan untuk menggunakan @JoinColumndiCompany
Óscar López
10
@MykhayloAdamovych: Tidak, itu sebenarnya kurang tepat. Jika Branchtidak memiliki properti yang mereferensikan Company, tetapi tabel di bawahnya memiliki kolom yang memiliki, maka Anda dapat menggunakannya @JoinTableuntuk memetakannya. Ini adalah situasi yang tidak biasa, karena Anda biasanya akan memetakan kolom di objek yang sesuai dengan tabelnya, tetapi itu bisa terjadi, dan itu sah-sah saja.
Tom Anderson
4
Ini adalah alasan lain untuk tidak menyukai ORM. Dokumentasinya sering terlalu cerdik, dan dalam buku-buku saya, ini berkelok-kelok di wilayah sihir terlalu banyak. Saya telah berjuang dengan masalah ini dan ketika diikuti kata demi kata untuk a @OneToOne, baris anak diperbarui dengan nulldi kolom FKey mereka yang mereferensikan orang tua.
Ashesh
225

@JoinColumnbisa digunakan di kedua sisi hubungan. Pertanyaannya adalah tentang penggunaan @JoinColumndi @OneToManysamping (kasus yang jarang terjadi). Dan intinya di sini adalah duplikasi informasi fisik (nama kolom) bersama dengan query SQL yang tidak dioptimalkan yang akan menghasilkan beberapa UPDATEpernyataan tambahan .

Menurut dokumentasi :

Karena banyak ke satu (hampir) selalu menjadi sisi pemilik hubungan dua arah dalam spesifikasi JPA, asosiasi satu ke banyak dijelaskan oleh@OneToMany(mappedBy=...)

@Entity
public class Troop {
    @OneToMany(mappedBy="troop")
    public Set<Soldier> getSoldiers() {
    ...
}

@Entity
public class Soldier {
    @ManyToOne
    @JoinColumn(name="troop_fk")
    public Troop getTroop() {
    ...
} 

Troopmemiliki hubungan dua arah dengan banyak hubungan Soldiermelalui properti pasukan. Anda tidak harus (tidak boleh) mendefinisikan pemetaan fisik apa pun di mappedBysamping.

Untuk memetakan satu arah dua arah ke banyak, dengan sisi satu ke banyak sebagai sisi pemilik , Anda harus menghapus mappedByelemen dan mengatur banyak ke satu @JoinColumnsebagai insertabledan updatableke false. Solusi ini tidak dioptimalkan dan akan menghasilkan beberapa UPDATEpernyataan tambahan .

@Entity
public class Troop {
    @OneToMany
    @JoinColumn(name="troop_fk") //we need to duplicate the physical information
    public Set<Soldier> getSoldiers() {
    ...
}

@Entity
public class Soldier {
    @ManyToOne
    @JoinColumn(name="troop_fk", insertable=false, updatable=false)
    public Troop getTroop() {
    ...
}
Mykhaylo Adamovych
sumber
1
Saya tidak dapat mengetahui bagaimana Pasukan dapat menjadi pemilik dalam cuplikan kedua Anda, Prajurit masih pemilik, karena berisi referensi kunci asing ke Pasukan. (Saya menggunakan mysql, saya memeriksa dengan pendekatan Anda).
Akhilesh
10
Dalam contoh Anda, penjelasannya mappedBy="troop"merujuk pada bidang yang mana?
Fractaliste
5
@Fractaliste anotasi mappedBy="troop"mengacu pada pasukan properti di kelas Soldier. Dalam kode di atas properti tidak terlihat karena di sini Mykhaylo dihilangkan, tetapi Anda dapat menyimpulkan keberadaannya dengan getter getTroop (). Periksa jawaban Óscar López , sangat jelas dan Anda akan mendapatkan intinya.
nicolimo86
1
Contoh ini adalah penyalahgunaan spesifikasi JPA 2. Jika tujuan penulis adalah untuk membuat hubungan dua arah maka harus menggunakan mappedBy di sisi induk, dan JoinColumn (jika perlu) di sisi anak. Dengan pendekatan yang disajikan di sini kita mendapatkan 2 hubungan searah: OneToMany dan ManyToOne yang independen tetapi hanya karena keberuntungan (lebih karena penyalahgunaan) kedua hubungan tersebut didefinisikan menggunakan kunci asing yang sama
aurelije
1
Jika Anda menggunakan JPA 2.x, jawaban saya di bawah ini sedikit lebih bersih. Meskipun saya sarankan mencoba kedua rute dan melihat apa yang Hibernate lakukan ketika menghasilkan tabel. Jika Anda sedang mengerjakan proyek baru, pilih generasi mana saja yang menurut Anda sesuai dengan kebutuhan Anda. Jika Anda menggunakan basis data lama dan tidak ingin mengubah struktur, pilih yang cocok dengan skema Anda.
Snekse
65

Karena ini adalah pertanyaan yang sangat umum, saya menulis artikel ini , yang menjadi dasar jawaban ini.

Asosiasi satu-ke-banyak searah

Seperti yang saya jelaskan di artikel ini , jika Anda menggunakan @OneToManyanotasi dengan @JoinColumn, maka Anda memiliki asosiasi searah, seperti yang terjadi antara Postentitas induk dan anak PostCommentdalam diagram berikut:

Asosiasi satu-ke-banyak searah

Saat menggunakan asosiasi satu-ke-banyak searah, hanya sisi induk yang memetakan asosiasi.

Dalam contoh ini, hanya Postentitas yang akan menentukan @OneToManyasosiasi ke PostCommententitas anak :

@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "post_id")
private List<PostComment> comments = new ArrayList<>();

Asosiasi dua arah dua arah

Jika Anda menggunakan @OneToManydengan mappedByatribut set, Anda memiliki hubungan dua arah. Dalam kasus kami, kedua Postentitas memiliki koleksi PostCommententitas anak, dan PostCommententitas anak memiliki referensi kembali ke Postentitas induk , seperti yang diilustrasikan oleh diagram berikut:

Asosiasi dua arah dua arah

Dalam PostCommententitas, postproperti entitas dipetakan sebagai berikut:

@ManyToOne(fetch = FetchType.LAZY)
private Post post;

Alasan kami menetapkan fetchatribut secara eksplisit FetchType.LAZYadalah karena, secara default, semua @ManyToOnedan @OneToOneasosiasi diambil dengan penuh semangat, yang dapat menyebabkan masalah kueri N +1. Untuk detail lebih lanjut tentang topik ini, lihat artikel ini .

Dalam Postentitas, commentsasosiasi dipetakan sebagai berikut:

@OneToMany(
    mappedBy = "post",
    cascade = CascadeType.ALL,
    orphanRemoval = true
)
private List<PostComment> comments = new ArrayList<>();

The mappedByatribut dari @OneToManyreferensi anotasi postproperti di anak PostCommententitas, dan, dengan cara ini, Hibernate tahu bahwa hubungan dua arah dikendalikan oleh @ManyToOnesisi, yang bertugas mengelola nilai kolom Foreign Key hubungan tabel ini didasarkan pada.

Untuk asosiasi dua arah, Anda juga perlu memiliki dua metode utilitas, seperti addChilddan removeChild:

public void addComment(PostComment comment) {
    comments.add(comment);
    comment.setPost(this);
}

public void removeComment(PostComment comment) {
    comments.remove(comment);
    comment.setPost(null);
}

Kedua metode ini memastikan bahwa kedua sisi dari asosiasi dua arah tidak sinkron. Tanpa menyinkronkan kedua ujungnya, Hibernate tidak menjamin bahwa perubahan status asosiasi akan menyebar ke database.

Untuk detail lebih lanjut tentang cara terbaik untuk menyinkronkan asosiasi dua arah dengan JPA dan Hibernate, lihat artikel ini .

Yang mana yang harus dipilih?

The searah @OneToManyasosiasi tidak melakukan dengan baik , sehingga Anda harus menghindarinya.

Anda lebih baik menggunakan bidirectional @OneToManyyang lebih efisien .

Vlad Mihalcea
sumber
32

Anotasi yang dipetakan oleh idealnya harus selalu digunakan di sisi Induk (kelas Perusahaan) dari hubungan dua arah, dalam hal ini harus dalam kelas Perusahaan yang menunjuk ke variabel anggota 'perusahaan' dari kelas Anak (kelas Cabang)

Anotasi @JoinColumn digunakan untuk menentukan kolom yang dipetakan untuk bergabung dengan entitas asosiasi, anotasi ini dapat digunakan di setiap kelas (Orangtua atau Anak) tetapi idealnya harus digunakan hanya di satu sisi (baik di kelas induk atau di kelas Anak tidak di keduanya) di sini dalam kasus ini saya menggunakannya di sisi Anak (kelas Cabang) dari hubungan dua arah yang menunjukkan kunci asing di kelas Cabang.

di bawah ini adalah contoh kerja:

kelas induk, Perusahaan

@Entity
public class Company {


    private int companyId;
    private String companyName;
    private List<Branch> branches;

    @Id
    @GeneratedValue
    @Column(name="COMPANY_ID")
    public int getCompanyId() {
        return companyId;
    }

    public void setCompanyId(int companyId) {
        this.companyId = companyId;
    }

    @Column(name="COMPANY_NAME")
    public String getCompanyName() {
        return companyName;
    }

    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    @OneToMany(fetch=FetchType.LAZY,cascade=CascadeType.ALL,mappedBy="company")
    public List<Branch> getBranches() {
        return branches;
    }

    public void setBranches(List<Branch> branches) {
        this.branches = branches;
    }


}

kelas anak, Cabang

@Entity
public class Branch {

    private int branchId;
    private String branchName;
    private Company company;

    @Id
    @GeneratedValue
    @Column(name="BRANCH_ID")
    public int getBranchId() {
        return branchId;
    }

    public void setBranchId(int branchId) {
        this.branchId = branchId;
    }

    @Column(name="BRANCH_NAME")
    public String getBranchName() {
        return branchName;
    }

    public void setBranchName(String branchName) {
        this.branchName = branchName;
    }

    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="COMPANY_ID")
    public Company getCompany() {
        return company;
    }

    public void setCompany(Company company) {
        this.company = company;
    }


}
Karang
sumber
20

Saya hanya ingin menambahkan yang @JoinColumntidak selalu harus terkait dengan lokasi informasi fisik seperti jawaban ini menyarankan. Anda bisa menggabungkan @JoinColumndengan @OneToManybahkan jika tabel induk tidak memiliki data tabel yang menunjuk ke tabel anak.

Cara mendefinisikan hubungan OneToMany searah di JPA

OneToMany searah, No Inverse ManyToOne, No Join Table

Tampaknya hanya tersedia di JPA 2.x+olah. Ini berguna untuk situasi di mana Anda ingin kelas anak hanya berisi ID dari orang tua, bukan referensi lengkap.

Snekse
sumber
Anda benar, dukungan untuk OneToMine searah diperkenalkan tanpa tabel di JPA2
aurelije
17

Saya tidak setuju dengan jawaban yang diterima di sini oleh Óscar López. Jawaban itu tidak akurat!

BUKAN @JoinColumnyang menunjukkan bahwa entitas ini adalah pemilik hubungan. Sebaliknya, itu adalah @ManyToOneanotasi yang melakukan ini (dalam contohnya).

Anotasi hubungan seperti @ManyToOne, @OneToManydan @ManyToManyberi tahu JPA / Hibernate untuk membuat pemetaan. Secara default, ini dilakukan melalui Tabel Gabung yang terpisah.


@JoinColumn

Tujuannya @JoinColumnadalah untuk membuat kolom bergabung jika belum ada. Jika ya, maka anotasi ini dapat digunakan untuk memberi nama kolom bergabung.


Dipetakan oleh

Tujuan dari MappedByparameter ini adalah untuk menginstruksikan JPA: JANGAN membuat tabel join lain karena hubungannya sudah dipetakan oleh entitas yang berlawanan dari hubungan ini.



Ingat: MappedByadalah properti anotasi hubungan yang tujuannya adalah untuk menghasilkan mekanisme untuk menghubungkan dua entitas yang secara default mereka lakukan dengan membuat tabel bergabung. MappedBymenghentikan proses itu dalam satu arah.

Entitas yang tidak menggunakan MappedBydikatakan sebagai pemilik hubungan karena mekanisme pemetaan didikte dalam kelasnya melalui penggunaan salah satu dari tiga penjelasan pemetaan terhadap bidang kunci asing. Ini tidak hanya menentukan sifat pemetaan tetapi juga menginstruksikan pembuatan tabel gabungan. Selain itu, opsi untuk menekan tabel bergabung juga ada dengan menerapkan penjelasan @JoinColumn di atas kunci asing yang menyimpannya di dalam tabel entitas pemilik.

Jadi dalam ringkasan: @JoinColumnbaik membuat kolom bergabung baru atau mengganti nama yang sudah ada; sementara MappedByparameter bekerja secara kolaboratif dengan penjelasan hubungan dari kelas (anak) lainnya untuk membuat pemetaan baik melalui tabel gabungan atau dengan membuat kolom kunci asing di tabel terkait dari entitas pemilik.

Untuk menggambarkan cara MapppedBykerjanya, pertimbangkan kode di bawah ini. Jika MappedByparameter dihapus, maka Hibernate sebenarnya akan membuat DUA tabel bergabung! Mengapa? Karena ada simetri dalam hubungan banyak ke banyak dan Hibernate tidak memiliki alasan untuk memilih satu arah di atas yang lain.

Karena itu kami gunakan MappedByuntuk memberi tahu Hibernate, kami telah memilih entitas lain untuk menentukan pemetaan hubungan antara dua entitas.

@Entity
public class Driver {
    @ManyToMany(mappedBy = "drivers")
    private List<Cars> cars;
}

@Entity
public class Cars {
    @ManyToMany
    private List<Drivers> drivers;
}

Menambahkan @JoinColumn (nama = "driverID") di kelas pemilik (lihat di bawah), akan mencegah pembuatan tabel bergabung dan sebagai gantinya, membuat kolom kunci asing driverID di tabel Mobil untuk membuat pemetaan:

@Entity
public class Driver {
    @ManyToMany(mappedBy = "drivers")
    private List<Cars> cars;
}

@Entity
public class Cars {
    @ManyToMany
    @JoinColumn(name = "driverID")
    private List<Drivers> drivers;
}
Iqbal Hamid
sumber
1

JPA adalah API berlapis, level yang berbeda memiliki anotasi sendiri. Level tertinggi adalah (1) Level entitas yang menggambarkan kelas persisten, maka Anda memiliki (2) level database relasional yang menganggap entitas dipetakan ke database relasional dan (3) model java.

Level 1 anotasi: @Entity, @Id, @OneToOne, @OneToMany, @ManyToOne, @ManyToMany. Anda dapat memperkenalkan kegigihan dalam aplikasi Anda menggunakan anotasi tingkat tinggi ini sendirian. Tapi kemudian Anda harus membuat database Anda sesuai dengan asumsi yang dibuat JPA. Anotasi ini menentukan entitas / model hubungan.

Level 2 penjelasan: @Table, @Column, @JoinColumn, ... Mempengaruhi pemetaan dari entitas / properti untuk tabel database relasional / kolom jika Anda tidak puas dengan default JPA atau jika Anda perlu untuk memetakan ke database yang sudah ada. Anotasi ini dapat dilihat sebagai anotasi implementasi, mereka menentukan bagaimana pemetaan harus dilakukan.

Menurut pendapat saya yang terbaik adalah tetap menempel sebanyak mungkin pada anotasi tingkat tinggi dan kemudian memperkenalkan anotasi tingkat rendah sesuai kebutuhan.

Untuk menjawab pertanyaan: @OneToMany/ mappedByadalah yang terbaik karena hanya menggunakan anotasi dari domain entitas. The @oneToMany/ @JoinColumnjuga baik-baik saja tetapi menggunakan anotasi implementasi di mana ini tidak sepenuhnya diperlukan.

Bruno Ranschaert
sumber
1

Biarkan saya membuatnya sederhana.
Anda dapat menggunakan @JoinColumn di kedua sisi terlepas dari pemetaan.

Mari kita bagi menjadi tiga kasus.
1) Pemetaan dua arah dari Cabang ke Perusahaan.
2) Pemetaan dua arah dari Perusahaan ke Cabang.
3) Hanya pemetaan dua arah dari Perusahaan ke Cabang.

Jadi setiap kasus penggunaan akan termasuk dalam tiga kategori ini. Jadi izinkan saya menjelaskan bagaimana cara menggunakan @JoinColumn dan mappedBy .
1) Pemetaan dua arah dari Cabang ke Perusahaan.
Gunakan JoinColumn di tabel Cabang.
2) Pemetaan dua arah dari Perusahaan ke Cabang.
Gunakan mappedBy di tabel Company seperti dijelaskan oleh jawaban @Mykhaylo Adamovych.
3) Pemetaan dua arah dari Perusahaan ke Cabang.
Cukup gunakan @JoinColumn di tabel Perusahaan.

@Entity
public class Company {

@OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY)
@JoinColumn(name="courseId")
private List<Branch> branches;
...
}

Ini mengatakan bahwa berdasarkan pemetaan "keyId" kunci asing di tabel cabang, dapatkan saya daftar semua cabang. CATATAN: Anda tidak dapat mengambil perusahaan dari cabang dalam kasus ini, hanya pemetaan uni-directional yang ada dari perusahaan ke cabang.

Vinjamuri Satya Krishna
sumber