Hubungan Banyak-ke-Banyak di JPA

1. Pengenalan

Dalam tutorial ini, kita akan melihat pelbagai cara untuk menangani hubungan antara banyak dengan menggunakan JPA .

Untuk mengemukakan idea, kami akan menggunakan model pelajar, kursus, dan pelbagai hubungan antara mereka.

Demi kesederhanaan, dalam contoh kod, kami hanya akan menunjukkan atribut dan konfigurasi JPA yang berkaitan dengan hubungan banyak-ke-banyak.

2. Asas Banyak-Banyak

2.1. Memodelkan Hubungan Banyak-ke-Banyak

Hubungan adalah hubungan antara dua jenis entiti. Sekiranya terdapat hubungan banyak-ke-banyak, kedua-dua belah pihak dapat berhubungan dengan banyak kejadian dari sisi lain.

Perhatikan, ada kemungkinan jenis entiti menjalin hubungan dengan mereka sendiri. Sebagai contoh, ketika kita membuat model pohon keluarga: setiap simpul adalah orang, jadi jika kita bercakap mengenai hubungan ibu bapa-anak, kedua-dua peserta akan menjadi orang.

Walau bagaimanapun, tidak membuat perbezaan sama ada kita membincangkan hubungan antara jenis entiti tunggal atau pelbagai. Oleh kerana lebih mudah untuk memikirkan hubungan antara dua jenis entiti yang berbeza, kami akan menggunakannya untuk menggambarkan kes kami.

Contohnya, apabila pelajar menandakan kursus yang mereka suka: pelajar boleh menyukai banyak kursus, dan banyak pelajar boleh menyukai kursus yang sama:

Seperti yang kita tahu, dalam RDBMSes kita dapat menjalin hubungan dengan kunci asing. Oleh kerana kedua-dua belah pihak dapat merujuk yang lain, kita perlu membuat jadual yang terpisah untuk menyimpan kunci asing :

Jadual seperti itu dipanggil jadual bergabung . Perhatikan, bahawa dalam gabungan jadual, gabungan kunci asing akan menjadi kunci utama gabungannya.

2.2. Pelaksanaan dalam JPA

Memodelkan hubungan banyak-banyak dengan POJO adalah mudah. Kita harus memasukkan Koleksi di kedua kelas , yang mengandungi unsur-unsur yang lain.

Selepas itu, kita perlu menandakan kelas dengan @Entity , dan kunci utama dengan @Id untuk menjadikan mereka entiti JPA yang betul.

Kita juga harus mengkonfigurasi jenis hubungan. Oleh itu, kami menandakan 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 dalam RDBMS.

Bahagian pemilik adalah tempat kita mengkonfigurasi hubungan, yang untuk contoh ini kita akan memilih kelas Pelajar .

Kita boleh melakukannya dengan penjelasan @JoinTable di kelas Pelajar . Kami memberikan nama jadual gabungan ( course_like ), dan kunci asing dengan anotasi @JoinColumn . The joinColumn atribut akan menyambung ke sebelah 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 bahawa penggunaan @JoinTable , atau bahkan @JoinColumn tidak diperlukan: JPA akan menghasilkan nama jadual dan lajur untuk kami. Namun, strategi yang digunakan JPA tidak akan selalu sesuai dengan konvensyen penamaan yang kita gunakan. Oleh itu kemungkinan untuk mengkonfigurasi nama jadual dan lajur.

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

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

Perhatikan, bahawa kerana hubungan banyak-ke-banyak tidak mempunyai sisi pemilik dalam pangkalan data , kami dapat mengkonfigurasi jadual bergabung di kelas Kursus dan merujuknya dari kelas Pelajar .

3. Banyak-ke-Banyak Menggunakan Kekunci Komposit

3.1. Atribut Hubungan Pemodelan

Katakan kita mahu membiarkan pelajar menilai kursus. Pelajar boleh menilai sebilangan kursus, dan sebilangan pelajar boleh menilai kursus yang sama. Oleh itu, ia juga merupakan hubungan banyak-ke-banyak. Apa yang menjadikannya lebih rumit adalah bahawa terdapat lebih banyak hubungan hubungan daripada fakta bahawa ia wujud. Kita perlu menyimpan skor penilaian yang diberikan oleh pelajar dalam kursus.

Di mana kita boleh menyimpan maklumat ini? Kami tidak dapat memasukkannya ke dalam entiti Pelajar kerana pelajar dapat memberi penilaian yang berbeza untuk kursus yang berbeza. Begitu juga, menyimpannya di entiti Kursus juga bukan penyelesaian yang baik.

Ini adalah keadaan apabila hubungan itu sendiri mempunyai sifat .

Menggunakan contoh ini, melampirkan atribut ke hubungan kelihatan seperti ini dalam rajah ER:

Kita dapat memperagakannya dengan cara yang hampir sama dengan hubungan sederhana-banyak-banyak. Satu-satunya perbezaan adalah kita melampirkan atribut baru ke jadual bergabung:

3.2. Membuat Kunci Gabungan dalam JPA

Pelaksanaan hubungan banyak-ke-banyak yang sederhana agak mudah. Satu-satunya masalah ialah kita tidak dapat menambahkan harta benda pada hubungan dengan cara itu, kerana kita menghubungkan entiti secara langsung. Oleh itu, kami tidak mempunyai cara untuk menambahkan harta pada hubungan itu sendiri .

Oleh kerana kita memetakan atribut DB ke medan kelas di JPA, kita perlu membuat kelas entiti baru untuk hubungan tersebut.

Sudah tentu, setiap entiti JPA memerlukan kunci utama. Kerana kunci utama kami adalah kunci gabungan, kami harus membuat kelas baru, yang akan memegang bahagian kunci yang berbeza:

@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 bahawa terdapat beberapa syarat utama, yang harus dipenuhi oleh kelas kunci komposit :

  • Kita mesti menandakannya dengan @Embeddable
  • Ia mesti melaksanakan java.io.Serializable
  • Kita perlu menyediakan pelaksanaan kodcincang () dan sama dengan () kaedah
  • Tiada bidang yang boleh menjadi entiti itu sendiri

3.3. Menggunakan Kunci Komposit dalam JPA

Dengan menggunakan kelas kunci komposit ini, kita dapat membuat kelas entiti, yang memodelkan jadual bergabung:

@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 }

Kod ini sangat mirip dengan pelaksanaan entiti biasa. Walau bagaimanapun, kami mempunyai beberapa perbezaan 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 memodelkannya dalam JPA. Ketiganya mempunyai kelebihan dan kekurangan yang berbeza dalam hal:

  • kejelasan kod
  • Kejelasan DB
  • keupayaan untuk memberikan atribut kepada hubungan
  • berapa banyak entiti yang dapat kita kaitkan dengan hubungan, dan
  • sokongan untuk pelbagai hubungan antara entiti yang sama

Seperti biasa, contohnya terdapat di GitHub.