Jenis Gabungan JPA

1. Gambaran keseluruhan

Dalam tutorial ini, kita akan melihat pelbagai jenis gabungan yang disokong oleh JPA.

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

2. Contoh Model Data

Mari lihat contoh model data yang akan kami gunakan dalam contoh.

Pertama, kami akan mewujudkan entiti Pekerja :

@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 Pekerja akan ditugaskan hanya kepada satu Jabatan :

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

Akhir sekali, setiap Pekerja akan mempunyai beberapa Telefon :

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

3. Penyertaan Dalaman

Kita akan mulakan dengan penyertaan dalaman. Apabila dua atau lebih entiti bergabung, hanya rekod yang sesuai dengan keadaan bergabung yang dikumpulkan dalam hasilnya.

3.1. Penyertaan Batin yang Tersirat dengan Navigasi Persatuan Tunggal

Penyertaan dalaman boleh tersirat. Seperti namanya, pembangun tidak menentukan penyertaan dalaman tersirat . Setiap kali kami menavigasi persatuan bernilai tunggal, JPA secara automatik membuat gabungan tersirat:

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

Di sini, entiti Pekerja mempunyai hubungan banyak-dengan-satu dengan entiti Jabatan . Sekiranya kita menavigasi dari entiti Karyawan ke Jabatannya - menentukan bahagian e - kita akan menavigasi persatuan bernilai tunggal. Hasilnya, JPA akan membuat gabungan dalaman. Selanjutnya, syarat bergabung akan berasal dari pemetaan metadata.

3.2. Inner Eksplisit Bergabung dengan Persatuan Tunggal

Seterusnya, kita akan melihat gabungan dalaman yang eksplisit di mana kita menggunakan kata kunci JOIN dalam pertanyaan JPQL kami:

@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 pertanyaan ini, kami menentukan kata kunci JOIN dan entiti Jabatan yang berkaitan dalam klausa FROM , sedangkan yang sebelumnya tidak dinyatakan sama sekali. Namun, selain perbezaan sintaksis ini, pertanyaan SQL yang dihasilkan akan sangat serupa.

Kami juga dapat menentukan kata kunci INNER pilihan:

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

Oleh kerana JPA secara implisit akan bergabung dalam, kapan kita perlu bersikap eksplisit?

Pertama, JPA membuat gabungan dalaman tersirat hanya apabila kita menentukan ungkapan jalan. Sebagai contoh, apabila kita ingin memilih hanya Karyawan yang mempunyai Jabatan dan kita tidak menggunakan ungkapan jalan - e.department - , kita harus menggunakan kata kunci JOIN dalam pertanyaan kita.

Kedua, apabila kita jelas, lebih mudah mengetahui apa yang sedang berlaku.

3.3. Inner Eksplisit Bergabung dengan Persatuan Berharga Koleksi

Tempat lain yang perlu kita jelaskan ialah dengan persatuan yang bernilai koleksi.

Sekiranya kita melihat model data kita, Pekerja mempunyai hubungan satu-dengan-banyak dengan Telefon . Seperti contoh sebelumnya, kita boleh mencuba menulis pertanyaan yang serupa:

SELECT e.phones FROM Employee e

Tetapi ini tidak akan berjaya seperti yang kita kehendaki . Oleh kerana persatuan yang dipilih - e.phone - bernilai koleksi, kami akan mendapat senarai Koleksi , dan bukannya entiti Telefon :

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

Lebih-lebih lagi, jika kita mahu menyaring entiti Telefon dalam klausa WHERE, JPA tidak akan membenarkannya. Ini kerana ungkapan jalan tidak dapat dilanjutkan dari persatuan bernilai koleksi . Oleh itu, sebagai contoh, nombor e.phones.number tidak sah .

Sebaliknya, kita harus membuat gabungan dalaman yang eksplisit dan membuat alias untuk entiti Telefon . Kemudian kita dapat menentukan entiti Telefon dalam 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. Sambungan Luar

Apabila dua atau lebih entiti bergabung luar, rekod yang memenuhi syarat bergabung dan juga catatan di entiti kiri dikumpulkan dalam hasilnya:

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

Di sini, hasilnya akan mengandungi Jabatan yang mempunyai kaitan dengan Pekerja dan juga yang tidak mempunyai apa-apa.

Ini juga disebut sebagai gabungan luar kiri. JPA tidak memberikan gabungan yang betul di mana kami juga mengumpulkan rekod yang tidak sepadan dari entiti yang betul. Walaupun kita dapat mensimulasikan gabungan yang betul dengan menukar entiti dalam klausa FROM.

5. Bergabung dalam Fasal DI MANA

5.1. Dengan Keadaan

Kita boleh menyenaraikan dua entiti dalam klausa FROM dan kemudian menentukan syarat bergabung dalam klausa DI MANA .

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.

Kami juga dapat melakukan penggabungan pengambilan luar dengan cara yang serupa dengan gabungan luar, di mana kami mengumpulkan rekod dari entiti kiri yang tidak sesuai dengan keadaan bergabung. Dan sebagai tambahan, ia dengan bersemangat memuat perkaitan 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

Dalam artikel ini, kami telah membahas jenis gabungan JPA.

Seperti biasa, anda boleh melihat semua sampel untuk ini dan tutorial lain di GitHub.