Jenis Bergabung JPA

1. Ikhtisar

Dalam tutorial ini, kita akan melihat tipe gabungan berbeda yang didukung oleh JPA.

Untuk tujuan itu, kami akan menggunakan JPQL, bahasa kueri untuk JPA.

2. Contoh Model Data

Mari kita lihat model data sampel kita yang akan kita gunakan dalam contoh.

Pertama, kami akan membuat entitas Karyawan :

@Entity public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; private String name; private int age; @ManyToOne private Department department; @OneToMany(mappedBy = "employee") private List phones; // getters and setters... }

Setiap Karyawan hanya akan ditugaskan ke satu Departemen :

@Entity public class Department { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; private String name; @OneToMany(mappedBy = "department") private List employees; // getters and setters... }

Terakhir, setiap Karyawan akan memiliki beberapa Telepon :

@Entity public class Phone { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; private String number; @ManyToOne private Employee employee; // getters and setters... }

3. Gabungan Batin

Kami akan mulai dengan gabungan dalam. Saat dua entitas atau lebih digabungkan, hanya rekaman yang cocok dengan kondisi gabungan yang dikumpulkan dalam hasil.

3.1. Gabungan Dalam Tersirat dengan Navigasi Asosiasi Nilai-Tunggal

Gabungan dalam bisa implisit. Seperti namanya, pengembang tidak menentukan gabungan dalam implisit . Setiap kali kami menavigasi asosiasi bernilai tunggal, JPA secara otomatis membuat gabungan implisit:

@Test public void whenPathExpressionIsUsedForSingleValuedAssociation_thenCreatesImplicitInnerJoin() { TypedQuery query = entityManager.createQuery( "SELECT e.department FROM Employee e", Department.class); List resultList = query.getResultList(); // Assertions... }

Di sini, entitas Karyawan memiliki hubungan banyak-ke-satu dengan entitas Departemen . Jika kita menavigasi dari entitas Karyawan ke Departemennya - menentukan e.department - kita akan menavigasi asosiasi bernilai tunggal. Akibatnya, JPA akan membuat gabungan dalam. Selanjutnya kondisi penggabungan akan diturunkan dari metadata pemetaan.

3.2. Penggabungan Batin Eksplisit dengan Asosiasi Nilai-Tunggal

Selanjutnya, kita akan melihat inner joins eksplisit di mana kita menggunakan kata kunci JOIN dalam kueri JPQL kita:

@Test public void whenJoinKeywordIsUsed_thenCreatesExplicitInnerJoin() { TypedQuery query = entityManager.createQuery( "SELECT d FROM Employee e JOIN e.department d", Department.class); List resultList = query.getResultList(); // Assertions... }

Dalam kueri ini, kami menetapkan kata kunci JOIN dan entitas Departemen terkait di klausa FROM , sedangkan di klausa sebelumnya tidak ditentukan sama sekali. Namun, selain perbedaan sintaksis ini, kueri SQL yang dihasilkan akan sangat mirip.

Kami juga dapat menentukan kata kunci INNER opsional:

@Test public void whenInnerJoinKeywordIsUsed_thenCreatesExplicitInnerJoin() { TypedQuery query = entityManager.createQuery( "SELECT d FROM Employee e INNER JOIN e.department d", Department.class); List resultList = query.getResultList(); // Assertions... }

Jadi karena JPA secara implisit akan menjadi gabungan dalam, kapan kita perlu menjelaskannya secara eksplisit?

Pertama, JPA membuat gabungan dalam implisit hanya ketika kita menentukan ekspresi jalur. Misalnya, ketika kita ingin memilih hanya Karyawan yang memiliki Departemen dan kita tidak menggunakan ekspresi jalur - e.department - , kita harus menggunakan kata kunci JOIN dalam kueri kita.

Kedua, ketika kita eksplisit, akan lebih mudah untuk mengetahui apa yang sedang terjadi.

3.3. Gabungan Batin Eksplisit dengan Asosiasi Berharga Koleksi

Tempat lain yang perlu kita jelaskan adalah dengan asosiasi bernilai koleksi.

Jika kita melihat model data kita, Karyawan memiliki hubungan satu-ke-banyak dengan Telepon . Seperti pada contoh sebelumnya, kita dapat mencoba menulis kueri serupa:

SELECT e.phones FROM Employee e

Tapi ini tidak akan bekerja seperti yang mungkin kita inginkan. Karena asosiasi yang dipilih - e.phones - bernilai koleksi, kita akan mendapatkan daftar Collection , alih-alih entitas Telepon :

@Test public void whenCollectionValuedAssociationIsSpecifiedInSelect_ThenReturnsCollections() { TypedQuery query = entityManager.createQuery( "SELECT e.phones FROM Employee e", Collection.class); List resultList = query.getResultList(); //Assertions }

Selain itu, jika kami ingin memfilter entitas Telepon di klausa WHERE, JPA tidak akan mengizinkannya. Ini karena ekspresi jalur tidak dapat melanjutkan dari asosiasi bernilai koleksi . Jadi misalnya, e.phones.number tidak valid .

Sebagai gantinya, kita harus membuat gabungan dalam eksplisit dan membuat alias untuk entitas Telepon . Kemudian kita dapat menentukan entitas Telepon di klausa SELECT atau WHERE:

@Test public void whenCollectionValuedAssociationIsJoined_ThenCanSelect() { TypedQuery query = entityManager.createQuery( "SELECT ph FROM Employee e JOIN e.phones ph WHERE ph LIKE '1%'", Phone.class); List resultList = query.getResultList(); // Assertions... }

4. Gabung Luar

When two or more entities are outer-joined, the records that satisfy the join condition and also the records in the left entity are collected in the result:

@Test public void whenLeftKeywordIsSpecified_thenCreatesOuterJoinAndIncludesNonMatched() { TypedQuery query = entityManager.createQuery( "SELECT DISTINCT d FROM Department d LEFT JOIN d.employees e", Department.class); List resultList = query.getResultList(); // Assertions... }

Here, the result will contain Departments that have associated Employees and also the ones that don't have any.

This is also referred to as a left outer join. JPA doesn't provide right joins where we also collect unmatching records from the right entity. Though we can simulate right joins by swapping entities in the FROM clause.

5. Joins in the WHERE Clause

5.1. With a Condition

We can list two entities in the FROM clause andthen specify the join condition in the WHERE clause.

This can be handy especially when database level foreign keys aren't in place:

@Test public void whenEntitiesAreListedInFromAndMatchedInWhere_ThenCreatesJoin() { TypedQuery query = entityManager.createQuery( "SELECT d FROM Employee e, Department d WHERE e.department = d", Department.class); List resultList = query.getResultList(); // Assertions... }

Here, we're joining Employee and Department entities, but this time specifying a condition in the WHERE clause.

5.2. Without a Condition (Cartesian Product)

Similarly, we can list two entities in the FROM clause without specifying any join condition. In this case, we'll get a cartesian product back. This means that every record in the first entity is paired with every other record in the second entity:

@Test public void whenEntitiesAreListedInFrom_ThenCreatesCartesianProduct() { TypedQuery query = entityManager.createQuery( "SELECT d FROM Employee e, Department d", Department.class); List resultList = query.getResultList(); // Assertions... }

As we can guess, these kinds of queries won't perform well.

6. Multiple Joins

So far, we've used two entities to perform joins, but this isn't a rule. We can also join multiple entities in a single JPQL query:

@Test public void whenMultipleEntitiesAreListedWithJoin_ThenCreatesMultipleJoins() { TypedQuery query = entityManager.createQuery( "SELECT ph FROM Employee e JOIN e.department d JOIN e.phones ph WHERE d.name IS NOT NULL", Phone.class); List resultList = query.getResultList(); // Assertions... }

Here, we're selecting all Phones of all Employees that have a Department. Similar to other inner joins, we're not specifying conditions since JPA extracts this information from mapping metadata.

7. Fetch Joins

Now, let's talk about fetch joins. Its primary usage is for fetching lazy-loaded associations eagerly for the current query.

Here, we'll eagerly load Employees association:

@Test public void whenFetchKeywordIsSpecified_ThenCreatesFetchJoin() { TypedQuery query = entityManager.createQuery( "SELECT d FROM Department d JOIN FETCH d.employees", Department.class); List resultList = query.getResultList(); // Assertions... }

Although this query looks very similar to other queries, there is one difference, and that is that the Employees are eagerly loaded. That means that once we call getResultList in the test above, the Department entities will have their employees field loaded, thus saving us another trip to the database.

But be aware of the memory trade-off. We may be more efficient because we only performed one query, but we also loaded all Departments and their employees into memory at once.

Kita juga dapat melakukan gabungan pengambilan luar dengan cara yang mirip dengan gabungan luar, di mana kami mengumpulkan rekaman dari entitas kiri yang tidak cocok dengan kondisi penggabungan. Dan sebagai tambahan, itu dengan bersemangat memuat asosiasi yang ditentukan:

@Test public void whenLeftAndFetchKeywordsAreSpecified_ThenCreatesOuterFetchJoin() { TypedQuery query = entityManager.createQuery( "SELECT d FROM Department d LEFT JOIN FETCH d.employees", Department.class); List resultList = query.getResultList(); // Assertions... }

8. Ringkasan

Pada artikel ini, kami telah membahas jenis gabungan JPA.

Seperti biasa, Anda dapat melihat semua contoh untuk ini dan tutorial lainnya di GitHub.