Hubungan Many-To-Many di JPA

1. Perkenalan

Dalam tutorial ini, kita akan melihat banyak cara untuk menangani hubungan banyak-ke-banyak menggunakan JPA .

Untuk mempresentasikan ide, kami akan menggunakan model siswa, mata kuliah, dan berbagai hubungan di antara mereka.

Demi kesederhanaan, dalam contoh kode, kami hanya akan menampilkan atribut dan konfigurasi JPA yang terkait dengan hubungan banyak-ke-banyak.

2. Basic Many-To-Many

2.1. Memodelkan Hubungan Banyak-ke-Banyak

Hubungan adalah hubungan antara dua jenis entitas. Dalam kasus hubungan banyak-ke-banyak, kedua sisi dapat berhubungan dengan beberapa contoh dari sisi lainnya.

Perhatikan, bahwa mungkin saja tipe entitas memiliki hubungan dengan dirinya sendiri. Misalnya, saat kita memodelkan silsilah keluarga: setiap simpul adalah orang, jadi jika kita berbicara tentang hubungan orang tua-anak, kedua peserta adalah orang.

Namun, tidak ada bedanya apakah kita berbicara tentang hubungan antara satu atau beberapa tipe entitas. Karena lebih mudah untuk memikirkan hubungan antara dua tipe entitas yang berbeda, kami akan menggunakannya untuk menggambarkan kasus kami.

Misalnya, ketika siswa menandai mata pelajaran yang mereka sukai: seorang siswa dapat menyukai banyak mata pelajaran, dan banyak siswa dapat menyukai mata pelajaran yang sama:

Seperti yang kita ketahui, dalam RDBMS kita dapat membuat hubungan dengan kunci asing. Karena kedua sisi harus dapat mereferensikan yang lain, kita perlu membuat tabel terpisah untuk menampung kunci asing :

Tabel seperti itu disebut tabel gabungan . Perhatikan, bahwa dalam tabel gabungan, kombinasi kunci asing akan menjadi kunci utama kompositnya.

2.2. Implementasi di JPA

Membuat model hubungan banyak-ke-banyak dengan POJO itu mudah. Kita harus menyertakan Koleksi di kedua kelas , yang berisi elemen lainnya.

Setelah itu, kita perlu menandai kelas dengan @Entity , dan kunci utama dengan @Id untuk menjadikannya entitas JPA yang tepat.

Selain itu, kita harus mengonfigurasi jenis hubungan. Karenanya kami menandai koleksi dengan anotasi @ManyToMany :

@Entity class Student { @Id Long id; @ManyToMany Set likedCourses; // additional properties // standard constructors, getters, and setters } @Entity class Course { @Id Long id; @ManyToMany Set likes; // additional properties // standard constructors, getters, and setters }

Selain itu, kita harus mengkonfigurasi cara memodelkan hubungan di RDBMS.

Sisi pemilik adalah tempat kami mengonfigurasi hubungan, yang untuk contoh ini kami akan memilih kelas Mahasiswa .

Kita dapat melakukan ini dengan anotasi @JoinTable di kelas Siswa . Kami memberikan nama tabel gabungan ( course_like ), dan kunci asing dengan anotasi @JoinColumn . The joinColumn atribut akan terhubung ke sisi pemilik hubungan, dan inverseJoinColumn ke sisi lain:

@ManyToMany @JoinTable( name = "course_like", joinColumns = @JoinColumn(name = "student_id"), inverseJoinColumns = @JoinColumn(name = "course_id")) Set likedCourses;

Perhatikan, bahwa menggunakan @JoinTable , atau bahkan @JoinColumn tidak diperlukan: JPA akan membuat nama tabel dan kolom untuk kita. Namun, strategi yang digunakan JPA tidak selalu sesuai dengan konvensi penamaan yang kami gunakan. Karenanya kemungkinan untuk mengkonfigurasi nama tabel dan kolom.

Di sisi target, kami hanya perlu memberikan nama bidang, yang memetakan hubungan . Oleh karena itu, kami menetapkan atribut mappedBy dari anotasi @ManyToMany di kelas Kursus :

@ManyToMany(mappedBy = "likedCourses") Set likes;

Perhatikan, karena hubungan banyak-ke-banyak tidak memiliki sisi pemilik dalam database , kita dapat mengonfigurasi tabel gabungan di kelas Kursus dan mereferensikannya dari kelas Siswa .

3. Banyak-ke-Banyak Menggunakan Kunci Komposit

3.1. Modeling Relationship Attributes

Katakanlah kita ingin membiarkan siswa menilai kursus. Seorang siswa dapat memberi peringkat pada sejumlah kursus, dan sejumlah siswa dapat memberi peringkat pada kursus yang sama. Oleh karena itu, ini juga merupakan hubungan banyak-ke-banyak. Apa yang membuatnya sedikit lebih rumit adalah bahwa ada lebih banyak hubungan pemeringkatan daripada fakta bahwa hubungan itu ada. Kita perlu menyimpan skor penilaian yang diberikan siswa pada kursus.

Di mana kami dapat menyimpan informasi ini? Kami tidak dapat memasukkannya ke dalam entitas Siswa karena siswa dapat memberikan peringkat yang berbeda untuk kursus yang berbeda. Demikian pula, menyimpannya di entitas Kursus juga bukan solusi yang baik.

Ini adalah situasi ketika hubungan itu sendiri memiliki atribut .

Menggunakan contoh ini, melampirkan atribut ke relasi terlihat seperti ini dalam diagram ER:

Kita dapat memodelkannya hampir sama seperti yang kita lakukan dengan hubungan banyak-ke-banyak yang sederhana. Satu-satunya perbedaan adalah kita melampirkan atribut baru ke tabel gabungan:

3.2. Membuat Kunci Komposit di JPA

Penerapan hubungan banyak-ke-banyak yang sederhana agak mudah. Satu-satunya masalah adalah kita tidak dapat menambahkan properti ke relasi seperti itu, karena kita menghubungkan entitas secara langsung. Oleh karena itu, kami tidak memiliki cara untuk menambahkan properti ke hubungan itu sendiri .

Karena kita memetakan atribut DB ke bidang kelas di JPA, kita perlu membuat kelas entitas baru untuk hubungan tersebut.

Tentu saja, setiap entitas JPA membutuhkan kunci utama. Karena kunci utama kita adalah kunci komposit, kita harus membuat kelas baru, yang akan menampung berbagai bagian kunci:

@Embeddable class CourseRatingKey implements Serializable { @Column(name = "student_id") Long studentId; @Column(name = "course_id") Long courseId; // standard constructors, getters, and setters // hashcode and equals implementation }

Perhatikan, bahwa ada beberapa persyaratan utama, yang harus dipenuhi oleh kelas kunci komposit :

  • Kita harus menandainya dengan @Embeddable
  • Itu harus mengimplementasikan java.io.Serializable
  • Kita perlu menyediakan sebuah implementasi dari kode hash () dan equals () metode
  • Tidak ada satu pun bidang yang dapat menjadi entitas itu sendiri

3.3. Menggunakan Kunci Komposit di JPA

Dengan menggunakan kelas kunci komposit ini, kita dapat membuat kelas entitas, yang memodelkan tabel gabungan:

@Entity class CourseRating { @EmbeddedId CourseRatingKey id; @ManyToOne @MapsId("studentId") @JoinColumn(name = "student_id") Student student; @ManyToOne @MapsId("courseId") @JoinColumn(name = "course_id") Course course; int rating; // standard constructors, getters, and setters }

Kode ini sangat mirip dengan implementasi entitas biasa. Namun, kami memiliki beberapa perbedaan utama:

  • we used @EmbeddedId, to mark the primary key, which is an instance of the CourseRatingKey class
  • we marked the student and course fields with @MapsId

@MapsId means that we tie those fields to a part of the key, and they're the foreign keys of a many-to-one relationship. We need it, because as we mentioned above, in the composite key we can't have entities.

After this, we can configure the inverse references in the Student and Course entities like before:

class Student { // ... @OneToMany(mappedBy = "student") Set ratings; // ... } class Course { // ... @OneToMany(mappedBy = "course") Set ratings; // ... }

Note, that there's an alternative way to use composite keys: the @IdClass annotation.

3.4. Further Characteristics

We configured the relationships to the Student and Course classes as @ManyToOne. We could do this because with the new entity we structurally decomposed the many-to-many relationship to two many-to-one relationships.

Why were we able to do this? If we inspect the tables closely in the previous case, we can see, that it contained two many-to-one relationships. In other words, there isn't any many-to-many relationship in an RDBMS. We call the structures we create with join tables many-to-many relationships because that's what we model.

Besides, it's more clear if we talk about many-to-many relationships, because that's our intention. Meanwhile, a join table is just an implementation detail; we don't really care about it.

Moreover, this solution has an additional feature we didn't mention yet. The simple many-to-many solution creates a relationship between two entities. Therefore, we cannot expand the relationship to more entities. However, in this solution we don't have this limit: we can model relationships between any number of entity types.

For example, when multiple teachers can teach a course, students can rate how a specific teacher teaches a specific course. That way, a rating would be a relationship between three entities: a student, a course, and a teacher.

4. Many-to-Many With a New Entity

4.1. Modeling Relationship Attributes

Let's say we want to let students register to courses. Also, we need to store the point when a student registered for a specific course. On top of that, we also want to store what grade she received in the course.

In an ideal world, we could solve this with the previous solution, when we had an entity with a composite key. However, our world is far from ideal and students don't always accomplish a course on the first try.

In this case, there're multiple connections between the same student-course pairs, or multiple rows, with the same student_id-course_id pairs. We can't model it using any of the previous solutions because all primary keys must be unique. Therefore, we need to use a separate primary key.

Therefore, we can introduce an entity, which will hold the attributes of the registration:

In this case, the Registration entity represents the relationship between the other two entities.

Since it's an entity, it'll have its own primary key.

Note, that in the previous solution we had a composite primary key, which we created from the two foreign keys. Now, the two foreign keys won't be the part of the primary key:

4.2. Implementation in JPA

Since the coure_registration became a regular table, we can create a plain old JPA entity modeling it:

@Entity class CourseRegistration { @Id Long id; @ManyToOne @JoinColumn(name = "student_id") Student student; @ManyToOne @JoinColumn(name = "course_id") Course course; LocalDateTime registeredAt; int grade; // additional properties // standard constructors, getters, and setters }

Also, we need to configure the relationships in the Student and Course classes:

class Student { // ... @OneToMany(mappedBy = "student") Set registrations; // ... } class Course { // ... @OneToMany(mappedBy = "courses") Set registrations; // ... }

Again, we configured the relationship before. Hence, we only need to tell JPA, where can it find that configuration.

Note, that we could use this solution to address the previous problem: students rating courses. However, it feels weird to create a dedicated primary key unless we have to. Moreover, from an RDBMS perspective, it doesn't make much sense, since combining the two foreign keys made a perfect composite key. Besides, that composite key had a clear meaning: which entities we connect in the relationship.

Otherwise, the choice between these two implementations is often simply personal preference.

5. Conclusion

In this tutorial, we saw what a many-to-many relationship is and how can we model it in an RDBMS using JPA.

Kami melihat tiga cara untuk memodelkannya di JPA. Ketiganya memiliki kelebihan dan kekurangan yang berbeda dalam hal:

  • kejelasan kode
  • Kejelasan DB
  • kemampuan untuk menetapkan atribut ke hubungan
  • berapa banyak entitas yang dapat kita hubungkan dengan hubungan tersebut, dan
  • dukungan untuk banyak koneksi antara entitas yang sama

Seperti biasa, contoh tersedia di GitHub.