JPA-Join-Typen

1. Übersicht

In diesem Tutorial werden verschiedene von JPA unterstützte Join-Typen vorgestellt.

Zu diesem Zweck verwenden wir JPQL, eine Abfragesprache für JPA.

2. Beispieldatenmodell

Schauen wir uns unser Beispieldatenmodell an, das wir in den Beispielen verwenden werden.

Zunächst erstellen wir eine Mitarbeiterentität :

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

Jeder Mitarbeiter wird nur einer Abteilung zugeordnet :

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

Schließlich hat jeder Mitarbeiter mehrere Telefone :

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

3. Innere Verbindungen

Wir werden mit inneren Verknüpfungen beginnen. Wenn zwei oder mehr Entitäten innerlich verbunden sind, werden im Ergebnis nur die Datensätze erfasst, die der Verknüpfungsbedingung entsprechen.

3.1. Implizite innere Verknüpfung mit einwertiger Assoziationsnavigation

Innere Verknüpfungen können implizit sein. Wie der Name schon sagt, gibt der Entwickler keine impliziten inneren Verknüpfungen an . Immer wenn wir in einer einwertigen Zuordnung navigieren, erstellt JPA automatisch einen impliziten Join:

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

Hier hat die Mitarbeiterentität eine Eins-zu-Eins-Beziehung zur Abteilungsentität . Wenn wir von einer Mitarbeiterentität zu ihrer Abteilung navigieren und e.department angeben , navigieren wir zu einer einwertigen Zuordnung. Infolgedessen erstellt JPA einen inneren Join. Darüber hinaus wird die Join-Bedingung aus Mapping-Metadaten abgeleitet.

3.2. Explizite innere Verbindung mit einwertiger Assoziation

Als Nächstes betrachten wir explizite innere Verknüpfungen, bei denen wir das Schlüsselwort JOIN in unserer JPQL-Abfrage verwenden:

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

In dieser Abfrage haben wir in der FROM-Klausel ein JOIN-Schlüsselwort und die zugehörige Department- Entität angegeben , während sie in der vorherigen überhaupt nicht angegeben wurden. Abgesehen von diesem syntaktischen Unterschied sind die resultierenden SQL-Abfragen jedoch sehr ähnlich.

Wir können auch ein optionales INNER-Schlüsselwort angeben:

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

Also, da JPA implizit zu einem inneren Join führen wird, wann müssten wir explizit sein?

Erstens erstellt JPA nur dann einen impliziten inneren Join, wenn wir einen Pfadausdruck angeben. Wenn wir beispielsweise nur die Mitarbeiter auswählen möchten , die eine Abteilung haben und keinen Pfadausdruck verwenden - e.department - , sollten wir in unserer Abfrage das Schlüsselwort JOIN verwenden.

Zweitens, wenn wir explizit sind, kann es einfacher sein zu wissen, was los ist.

3.3. Explizite innere Verknüpfung mit Assoziationen mit Sammlungswert

Ein weiterer Punkt, den wir explizit angeben müssen, sind Assoziationen mit Sammlungswerten.

Wenn wir uns unser Datenmodell ansehen, hat der Mitarbeiter eine Eins-zu-Viele-Beziehung zu Telefon . Wie in einem früheren Beispiel können wir versuchen, eine ähnliche Abfrage zu schreiben:

SELECT e.phones FROM Employee e

Aber das wird nicht ganz so funktionieren, wie wir es vielleicht beabsichtigt haben. Da die ausgewählten Verein - e.phones - Sammlung bewertet ist, werden wir eine Liste bekommen Sammlung s, statt Telefongesellschaften :

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

Außerdem, wenn wir zu filternde Telefongesellschaften in WHERE - Klausel wird JPA nicht zulassen. Dies liegt daran, dass ein Pfadausdruck von einer Zuordnung mit Sammlungswert nicht fortgesetzt werden kann . So ist beispielsweise e.phones.number nicht gültig .

Stattdessen sollten wir einen expliziten inneren Join erstellen und einen Alias ​​für die Phone- Entität erstellen . Dann können wir die Phone- Entität in der SELECT- oder WHERE-Klausel angeben :

@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. Äußere Verbindung

Wenn zwei oder mehr Entitäten äußerlich verbunden sind, werden die Datensätze, die die Verknüpfungsbedingung erfüllen, sowie die Datensätze in der linken Entität im Ergebnis gesammelt:

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

Hier enthält das Ergebnis Abteilungen , denen Mitarbeiter zugeordnet sind, sowie Abteilungen , denen keine zugeordnet sind.

Dies wird auch als linker äußerer Join bezeichnet. JPA bietet keine richtigen Verknüpfungen, bei denen wir auch nicht übereinstimmende Datensätze von der richtigen Entität sammeln. Wir können jedoch Rechtsverknüpfungen simulieren, indem wir Entitäten in der FROM-Klausel austauschen.

5. Tritt der WHERE-Klausel bei

5.1. Mit einer Bedingung

Wir können zwei Entitäten in der FROM-Klausel auflisten und dann die Join-Bedingung in der WHERE-Klausel angeben .

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.

Wir können den äußeren Abruf-Join auch auf ähnliche Weise wie den äußeren Join ausführen, bei dem wir Datensätze von der linken Entität sammeln, die nicht der Join-Bedingung entsprechen. Außerdem wird die angegebene Zuordnung eifrig geladen:

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

In diesem Artikel haben wir uns mit JPA-Join-Typen befasst.

Wie immer können Sie alle Beispiele für dieses und andere Tutorials auf GitHub nachlesen.