Paruh pertama tutorial ini memperkenalkan dasar-dasar Java Persistence API dan menunjukkan kepada Anda cara mengonfigurasi aplikasi JPA menggunakan Hibernate 5.3.6 dan Java 8. Jika Anda telah membaca tutorial itu dan mempelajari contoh aplikasinya, maka Anda mengetahui dasar-dasar dari pemodelan entitas JPA dan hubungan banyak-ke-satu di JPA. Anda juga telah berlatih menulis kueri bernama dengan JPA Query Language (JPQL).
Di paruh kedua tutorial ini, kita akan membahas JPA dan Hibernate lebih dalam. Anda akan mempelajari cara membuat model hubungan banyak-ke-banyak antara Movie
dan SuperHero
entitas, menyiapkan repositori individual untuk entitas ini, dan mempertahankan entitas tersebut ke database dalam memori H2. Anda juga akan mempelajari lebih lanjut tentang peran operasi kaskade di JPA, dan mendapatkan tip untuk memilih CascadeType
strategi untuk entitas di database. Terakhir, kami akan mengumpulkan aplikasi yang berfungsi yang dapat Anda jalankan di IDE atau di baris perintah.
Tutorial ini berfokus pada dasar-dasar JPA, tetapi pastikan untuk memeriksa tip Java berikut yang memperkenalkan topik lanjutan di JPA:
- Hubungan pewarisan di JPA dan Hibernate
- Kunci komposit di JPA dan Hibernate
Hubungan banyak ke banyak di JPA
Relasi banyak ke banyak menentukan entitas yang kedua sisi relasinya dapat memiliki banyak referensi satu sama lain. Sebagai contoh, kami akan membuat model film dan pahlawan super. Tidak seperti contoh Penulis & Buku dari Bagian 1, sebuah film dapat memiliki beberapa pahlawan super, dan seorang pahlawan super dapat muncul di beberapa film. Pahlawan super kita, Ironman dan Thor, keduanya muncul dalam dua film, "The Avengers" dan "Avengers: Infinity War."
Untuk memodelkan hubungan banyak-ke-banyak ini menggunakan JPA, kita memerlukan tiga tabel:
- FILM
- SUPER HERO
- SUPERHERO_MOVIES
Gambar 1 menunjukkan model domain dengan tiga tabel.

Perhatikan bahwa SuperHero_Movies
adalah bergabung meja antara Movie
dan SuperHero
tabel. Di JPA, tabel gabungan adalah jenis tabel khusus yang memfasilitasi hubungan banyak-ke-banyak.
Searah atau dua arah?
Di JPA kami menggunakan @ManyToMany
anotasi untuk memodelkan hubungan banyak-ke-banyak. Jenis hubungan ini bisa searah atau dua arah:
- Dalam hubungan searah, hanya satu entitas dalam hubungan yang menunjuk ke entitas lainnya.
- Dalam hubungan dua arah, kedua entitas menunjuk satu sama lain.
Contoh kami adalah dua arah, yang berarti bahwa sebuah film menunjuk ke semua pahlawan supernya, dan seorang pahlawan super menunjuk ke semua film mereka. Dalam hubungan dua arah, banyak-ke-banyak, satu entitas memiliki hubungan dan entitas lainnya dipetakan ke hubungan. Kami menggunakan mappedBy
atribut @ManyToMany
anotasi untuk membuat pemetaan ini.
Kode 1 menunjukkan kode sumber untuk SuperHero
kelas.
Daftar 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 SuperHero
kelas memiliki beberapa penjelasan yang harus akrab dari Bagian 1:
@Entity
mengidentifikasiSuperHero
sebagai entitas JPA.@Table
memetakanSuperHero
entitas ke tabel "SUPER_HERO".
Perhatikan juga Integer
id
bidangnya, yang menentukan bahwa kunci utama tabel akan dibuat secara otomatis.
Selanjutnya kita akan melihat anotasi @ManyToMany
dan @JoinTable
.
Strategi pengambilan
Hal yang perlu diperhatikan dalam @ManyToMany
anotasi adalah cara kami mengonfigurasi strategi pengambilan , yang bisa malas atau bersemangat. Dalam hal ini, kita telah menyetel fetch
ke EAGER
, sehingga ketika kita mengambil SuperHero
dari database, kita juga akan secara otomatis mengambil semua yang sesuai Movie
.
Jika kami memilih untuk melakukan LAZY
pengambilan, kami hanya akan mengambil masing Movie
- masing seperti yang diakses secara khusus. Pengambilan malas hanya mungkin jika SuperHero
dilampirkan ke EntityManager
; jika tidak mengakses film pahlawan super akan menimbulkan pengecualian. Kami ingin dapat mengakses film pahlawan super sesuai permintaan, jadi dalam hal ini kami memilih EAGER
strategi pengambilan.
CascadeType.PERSIST
Operasi bertingkat menentukan bagaimana pahlawan super dan filmnya yang terkait disimpan ke dan dari database. Ada sejumlah konfigurasi tipe kaskade yang dapat dipilih, dan kita akan membicarakannya lebih banyak nanti di tutorial ini. Untuk saat ini, perhatikan saja bahwa kami telah menyetel cascade
atribut ke CascadeType.PERSIST
, yang berarti bahwa ketika kami menyimpan pahlawan super, filmnya juga akan disimpan.
Bergabunglah dengan tabel
JoinTable
adalah kelas yang memfasilitasi hubungan banyak-ke-banyak antara SuperHero
dan Movie
. Di kelas ini, kami mendefinisikan tabel yang akan menyimpan kunci primer untuk entitas SuperHero
dan Movie
entitas.
Kode 1 menentukan bahwa nama tabel akan SuperHero_Movies
. The bergabung kolom akan superhero_id
, dan inverse bergabung kolom akan movie_id
. The SuperHero
entitas memiliki hubungan, sehingga bergabung kolom akan diisi dengan SuperHero
's kunci utama. Kolom gabungan terbalik kemudian mereferensikan entitas di sisi lain hubungan, yaitu Movie
.
Berdasarkan definisi ini di Listing 1, kami mengharapkan sebuah tabel baru dibuat, bernama SuperHero_Movies
. Tabel akan memiliki dua kolom:, superhero_id
yang mereferensikan id
kolom SUPERHERO
tabel, dan movie_id
, yang mereferensikan id
kolom MOVIE
tabel.
Kelas Film
Kode 2 menunjukkan kode sumber untuk Movie
kelas. Ingatlah bahwa dalam hubungan dua arah, satu entitas memiliki hubungan (dalam kasus ini, SuperHero
) sementara entitas lainnya dipetakan ke hubungan tersebut. Kode dalam Listing 2 mencakup pemetaan hubungan yang diterapkan ke Movie
kelas.
Daftar 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 ini diterapkan ke @ManyToMany
penjelasan di Listing 2:
mappedBy
references the field name on theSuperHero
class that manages the many-to-many relationship. In this case, it references the movies field, which we defined in Listing 1 with the correspondingJoinTable
.cascade
is configured toCascadeType.PERSIST
, which means that when aMovie
is saved its correspondingSuperHero
entities should also be saved.fetch
tells theEntityManager
that it should retrieve a movie's superheroes eagerly: when it loads aMovie
, it should also load all correspondingSuperHero
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
Kita bisa mengimplementasikan semua kode persistensi kita langsung di aplikasi contoh, tetapi membuat kelas repositori memungkinkan kita memisahkan kode persistensi dari kode aplikasi. Sama seperti yang kami lakukan dengan aplikasi Buku & Penulis di Bagian 1, kami akan membuat EntityManager
dan menggunakannya untuk menginisialisasi dua repositori, satu untuk setiap entitas yang kami pertahankan.
Kode 3 menunjukkan kode sumber untuk MovieRepository
kelas.
Daftar 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(); } } } }
Ini MovieRepository
diinisialisasi dengan EntityManager
, lalu menyimpannya ke variabel anggota untuk digunakan dalam metode persistensi. Kami akan mempertimbangkan masing-masing metode ini.
Metode persistensi
Mari kita tinjau MovieRepository
metode persistensi dan lihat bagaimana mereka berinteraksi dengan EntityManager
metode persistensi.