Kegigihan Java dengan JPA dan Hibernate, Bahagian 2: Hubungan banyak-ke-banyak

Separuh pertama tutorial ini memperkenalkan asas-asas Java Persistence API dan menunjukkan kepada anda cara mengkonfigurasi aplikasi JPA menggunakan Hibernate 5.3.6 dan Java 8. Sekiranya anda telah membaca tutorial itu dan mempelajari aplikasi contohnya, maka anda tahu asas-asas memodelkan entiti JPA dan hubungan banyak-ke-satu dalam JPA. Anda juga pernah berlatih menulis pertanyaan bernama dengan JPA Query Language (JPQL).

Pada separuh kedua tutorial ini, kita akan lebih mendalam dengan JPA dan Hibernate. Anda akan belajar bagaimana memodelkan hubungan antara banyak Moviedan antara, dan SuperHeromenyediakan repositori individu untuk entiti ini, dan meneruskan entiti ke pangkalan data dalam memori H2. Anda juga akan mengetahui lebih lanjut mengenai peranan operasi lata dalam JPA, dan mendapatkan petua untuk memilih CascadeTypestrategi untuk entiti dalam pangkalan data. Akhirnya, kami akan mengumpulkan aplikasi yang boleh anda jalankan di IDE atau di baris arahan.

Tutorial ini memberi tumpuan kepada asas-asas JPA, tetapi pastikan anda melihat petua Java yang memperkenalkan topik yang lebih maju dalam JPA:

  • Hubungan warisan di JPA dan Hibernate
  • Kekunci komposit dalam JPA dan Hibernate
muat turun Dapatkan kod Muat turun kod sumber misalnya aplikasi yang digunakan dalam tutorial ini. Dicipta oleh Steven Haines untuk JavaWorld.

Hubungan banyak-ke-banyak di JPA

Hubungan banyak-ke-banyak menentukan entiti yang mana kedua-dua belah hubungan dapat mempunyai banyak rujukan antara satu sama lain. Sebagai contoh, kita akan memperagakan filem dan superhero. Tidak seperti contoh Pengarang & Buku dari Bahagian 1, sebuah filem boleh mempunyai banyak superhero, dan seorang superhero dapat muncul dalam beberapa filem. Pahlawan super kami, Ironman dan Thor, keduanya muncul dalam dua filem, "The Avengers" dan "Avengers: Infinity War."

Untuk memodelkan hubungan banyak-ke-banyak ini menggunakan JPA, kami memerlukan tiga jadual:

  • PERGERAKAN
  • SUPER_HERO
  • SUPERHERO_MOVIES

Rajah 1 menunjukkan model domain dengan tiga jadual.

Steven Haines

Perhatikan itu SuperHero_Moviesadalah jadual bergabung antara Moviedan SuperHerojadual. Di JPA, jadual bergabung adalah jenis jadual khas yang memudahkan hubungan antara banyak-banyak.

Tidak arah atau dua arah?

Dalam JPA kami menggunakan @ManyToManyanotasi untuk memodelkan hubungan banyak-ke-banyak. Jenis hubungan ini boleh menjadi searah atau dua arah:

  • Dalam hubungan tidak arah hanya satu entiti dalam hubungan itu menunjukkan yang lain.
  • Dalam hubungan dua arah kedua-dua entiti saling menunjuk.

Contoh kami adalah dua arah, yang bermaksud bahawa filem menunjuk ke semua pahlawan supernya, dan superhero menunjukkan semua filem mereka. Dalam hubungan dua arah, banyak-ke-banyak, satu entiti memiliki hubungan dan yang lain dipetakan ke hubungan. Kami menggunakan mappedByatribut @ManyToManyanotasi untuk membuat pemetaan ini.

Penyenaraian 1 menunjukkan kod sumber untuk SuperHerokelas.

Penyenaraian 1. SuperHero.java

 package com.geekcap.javaworld.jpa.model; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; import javax.persistence.Table; import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; @Entity @Table(name = "SUPER_HERO") public class SuperHero { @Id @GeneratedValue private Integer id; private String name; @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST) @JoinTable( name = "SuperHero_Movies", joinColumns = {@JoinColumn(name = "superhero_id")}, inverseJoinColumns = {@JoinColumn(name = "movie_id")} ) private Set movies = new HashSet(); public SuperHero() { } public SuperHero(Integer id, String name) { this.id = id; this.name = name; } public SuperHero(String name) { this.name = name; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Set getMovies() { return movies; } @Override public String toString() { return "SuperHero{" + "id=" + id + ", + name +"\'' + ", + movies.stream().map(Movie::getTitle).collect(Collectors.toList()) +"\'' + '}'; } } 

The SuperHerokelas mempunyai beberapa penjelasan yang perlu biasa daripada Bahagian 1:

  • @Entitymengenal pasti SuperHerosebagai entiti JPA.
  • @Tablememetakan SuperHeroentiti ke jadual "SUPER_HERO".

Perhatikan juga Integeridmedan, yang menentukan bahawa kunci utama jadual akan dihasilkan secara automatik.

Selanjutnya kita akan melihat @ManyToManydan @JoinTablepenjelasannya.

Mengambil strategi

Perkara yang perlu diperhatikan dalam @ManyToManypenjelasan adalah bagaimana kita mengkonfigurasi strategi pengambilan , yang boleh menjadi malas atau bersemangat. Dalam kes ini, kami telah menetapkan fetchuntuk EAGER, supaya apabila kita dapatkan SuperHerodari pangkalan data, kami akan juga secara automatik mendapatkan semula semua yang sepadan Movies.

Sekiranya kami memilih untuk melakukan LAZYpengambilan Moviesebagai gantinya, kami hanya akan mengambil masing-masing kerana ia diakses secara khusus. Pengambilan yang malas hanya boleh dilakukan semasa SuperHeromelekat pada EntityManager; jika tidak, mengakses filem superhero akan menimbulkan pengecualian. Kami ingin dapat mengakses filem superhero berdasarkan permintaan, jadi dalam hal ini kami memilih EAGERstrategi pengambilan.

CascadeType.PERSIST

Operasi kaskade menentukan bagaimana pahlawan super dan filemnya yang bersangkutan berterusan ke dan dari pangkalan data. Terdapat sebilangan besar konfigurasi jenis lata untuk dipilih, dan kami akan membincangkannya lebih lanjut kemudian dalam tutorial ini. Buat masa ini, perhatikan bahawa kami telah menetapkan cascadeatribut CascadeType.PERSIST, yang bermaksud bahawa ketika kami menyimpan superhero, filemnya juga akan disimpan.

Sertailah jadual

JoinTableadalah kelas yang memudahkan hubungan antara-ke-banyak antara SuperHerodan Movie. Di kelas ini, kami menentukan jadual yang akan menyimpan kunci utama untuk kedua-dua SuperHerodan Movieentiti.

Penyenaraian 1 menentukan bahawa nama jadual akan SuperHero_Movies. Yang menyertai lajur akan menjadi superhero_id, dan songsang menyertai lajur akan movie_id. The SuperHeroentiti memiliki hubungan, jadi menyertai lajur akan diisi dengan SuperHerokunci utama 's. Lajur bergabung terbalik kemudian merujuk entiti di sisi lain hubungan, iaitu Movie.

Berdasarkan definisi ini dalam Penyenaraian 1, kami mengharapkan jadual baru dibuat, diberi nama SuperHero_Movies. Jadual akan mempunyai dua lajur:, superhero_idyang merujuk idlajur SUPERHEROjadual, dan movie_id, yang merujuk idlajur MOVIEjadual.

Kelas Filem

Penyenaraian 2 menunjukkan kod sumber untuk Moviekelas. Ingatlah bahawa dalam hubungan dua arah, satu entiti memiliki hubungan (dalam kes ini, SuperHero) sementara yang lain dipetakan ke hubungan. Kod dalam Penyenaraian 2 merangkumi pemetaan hubungan yang diterapkan ke Moviekelas.

Penyenaraian 2. Movie.java

 package com.geekcap.javaworld.jpa.model; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.ManyToMany; import javax.persistence.Table; import java.util.HashSet; import java.util.Set; @Entity @Table(name = "MOVIE") public class Movie { @Id @GeneratedValue private Integer id; private String title; @ManyToMany(mappedBy = "movies", cascade = CascadeType.PERSIST, fetch = FetchType.EAGER) private Set superHeroes = new HashSet(); public Movie() { } public Movie(Integer id, String title) { this.id = id; this.title = title; } public Movie(String title) { this.title = title; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public Set getSuperHeroes() { return superHeroes; } public void addSuperHero(SuperHero superHero) { superHeroes.add(superHero); superHero.getMovies().add(this); } @Override public String toString() { return "Movie{" + "id=" + id + ", + title +"\'' + '}'; } }

Properti berikut digunakan untuk @ManyToManypenjelasan dalam Penyenaraian 2:

  • mappedBy references the field name on the SuperHero class that manages the many-to-many relationship. In this case, it references the movies field, which we defined in Listing 1 with the corresponding JoinTable.
  • cascade is configured to CascadeType.PERSIST, which means that when a Movie is saved its corresponding SuperHero entities should also be saved.
  • fetch tells the EntityManager that it should retrieve a movie's superheroes eagerly: when it loads a Movie, it should also load all corresponding SuperHero entities.

Something else to note about the Movie class is its addSuperHero() method.

When configuring entities for persistence, it isn't enough to simply add a superhero to a movie; we also need to update the other side of the relationship. This means we need to add the movie to the superhero. When both sides of the relationship are configured properly, so that the movie has a reference to the superhero and the superhero has a reference to the movie, then the join table will also be properly populated.

We've defined our two entities. Now let's look at the repositories we'll use to persist them to and from the database.

Tip! Set both sides of the table

It's a common mistake to only set one side of the relationship, persist the entity, and then observe that the join table is empty. Setting both sides of the relationship will fix this.

JPA repositories

Kami dapat menerapkan semua kod ketekunan kami secara langsung dalam contoh aplikasi, tetapi membuat kelas repositori memungkinkan kami memisahkan kod ketekunan dari kod aplikasi. Sama seperti yang kami lakukan dengan aplikasi Buku & Pengarang di Bahagian 1, kami akan membuatnya EntityManagerdan kemudian menggunakannya untuk menginisialisasi dua repositori, satu untuk setiap entiti yang sedang kami jalani.

Penyenaraian 3 menunjukkan kod sumber untuk MovieRepositorykelas.

Penyenaraian 3. MovieRepository.java

 package com.geekcap.javaworld.jpa.repository; import com.geekcap.javaworld.jpa.model.Movie; import javax.persistence.EntityManager; import java.util.List; import java.util.Optional; public class MovieRepository { private EntityManager entityManager; public MovieRepository(EntityManager entityManager) { this.entityManager = entityManager; } public Optional save(Movie movie) { try { entityManager.getTransaction().begin(); entityManager.persist(movie); entityManager.getTransaction().commit(); return Optional.of(movie); } catch (Exception e) { e.printStackTrace(); } return Optional.empty(); } public Optional findById(Integer id) { Movie movie = entityManager.find(Movie.class, id); return movie != null ? Optional.of(movie) : Optional.empty(); } public List findAll() { return entityManager.createQuery("from Movie").getResultList(); } public void deleteById(Integer id) { // Retrieve the movie with this ID Movie movie = entityManager.find(Movie.class, id); if (movie != null) { try { // Start a transaction because we're going to change the database entityManager.getTransaction().begin(); // Remove all references to this movie by superheroes movie.getSuperHeroes().forEach(superHero -> { superHero.getMovies().remove(movie); }); // Now remove the movie entityManager.remove(movie); // Commit the transaction entityManager.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); } } } } 

Yang MovieRepositoryadalah dimulakan dengan EntityManager, kemudian menyimpannya ke pembolehubah ahli untuk digunakan dalam kaedah kegigihan itu. Kami akan mempertimbangkan setiap kaedah ini.

Kaedah ketekunan

Mari kita MovieRepositorykaji kaedah kegigihan dan lihat bagaimana mereka berinteraksi dengan EntityManagerkaedah kegigihan.