JPA-join-typen

1. Overzicht

In deze zelfstudie bekijken we verschillende soorten joins die worden ondersteund door JPA.

Voor dat doel gebruiken we JPQL, een querytaal voor JPA.

2. Voorbeeldgegevensmodel

Laten we eens kijken naar ons voorbeeldgegevensmodel dat we in de voorbeelden zullen gebruiken.

Ten eerste maken we een Werknemer entiteit:

@Entity openbare klasse Werknemer {@Id @GeneratedValue (strategie = GenerationType.IDENTITY) lange privé-ID; private String naam; privé int leeftijd; @ManyToOne privéafdeling; @OneToMany (mappedBy = "werknemer") privélijsttelefoons; // getters en setters ...}

Elk Werknemer wordt slechts aan één toegewezen afdeling:

@Entity public class Department {@Id @GeneratedValue (strategy = GenerationType.AUTO) privé lang ID; private String naam; @OneToMany (mappedBy = "department") privé Lijst medewerkers; // getters en setters ...}

Ten slotte elk Werknemer zal meerdere hebben Telefoons:

@Entity openbare klasse Telefoon {@Id @GeneratedValue (strategy = GenerationType.IDENTITY) lange privé-ID; privé String-nummer; @ManyToOne privé Medewerker; // getters en setters ...}

3. Innerlijke joins

We beginnen met innerlijke joins. Wanneer twee of meer entiteiten innerlijk zijn samengevoegd, worden alleen de records die voldoen aan de samenvoegvoorwaarde verzameld in het resultaat.

3.1. Impliciete Inner Join met Single-Valued Association-navigatie

Innerlijke joins kunnen zijn impliciet. Zoals de naam impliceert, de ontwikkelaar specificeert geen impliciete inner joins. Telkens wanneer we door een associatie met één waarde navigeren, maakt JPA automatisch een impliciete deelname aan:

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

Hier de Werknemer entiteit heeft een veel-op-een-relatie met de afdeling entiteit. Als we navigeren vanaf een Werknemer entiteit voor haar afdeling - specificeren e. afdeling - we navigeren door een vereniging met één waarde. Als gevolg hiervan zal JPA een innerlijke join creëren. Bovendien wordt de join-voorwaarde afgeleid van het in kaart brengen van metagegevens.

3.2. Expliciete innerlijke join met eenmalige associatie

Vervolgens zullen we kijken naar expliciet innerlijke joins waar we gebruiken het trefwoord JOIN in onze JPQL-query:

@Test openbare leegte whenJoinKeywordIsUsed_thenCreatesExplicitInnerJoin () {TypedQuery query = entityManager.createQuery ("SELECTEER d VAN Employee e JOIN e.department d", Department.class); Lijst resultList = query.getResultList (); // Beweringen ...}

In deze vraag, we hebben een JOIN-sleutelwoord gespecificeerd en het bijbehorende afdeling entiteit in de FROM-component, terwijl ze in de vorige helemaal niet werden gespecificeerd. Afgezien van dit syntactische verschil, zullen de resulterende SQL-query's echter sterk op elkaar lijken.

We kunnen ook een optioneel INNER-trefwoord specificeren:

@Test public void whenInnerJoinKeywordIsUsed_thenCreatesExplicitInnerJoin () {TypedQuery query = entityManager.createQuery ("SELECTEER d VAN Employee e INNER JOIN e.department d", Department.class); Lijst resultList = query.getResultList (); // Beweringen ...}

Dus aangezien de PPV impliciet naar een innerlijke join zal gaan, wanneer moeten we dan expliciet zijn?

Ten eerste, JPA maakt alleen een impliciete inner join als we een padexpressie specificeren. Als we bijvoorbeeld alleen de Werknemers met een afdeling en we gebruiken geen paduitdrukking - e. afdeling -, moeten we het trefwoord JOIN gebruiken in onze zoekopdracht.

Ten tweede, als we expliciet zijn, kan het gemakkelijker zijn om te weten wat er aan de hand is.

3.3. Expliciete innerlijke join met verzamelingswaardige associaties

Een andere plaats we moeten expliciet zijn is met verzamelingswaardige associaties.

Als we naar ons datamodel kijken, de Werknemer heeft een een-op-veel-relatie met Telefoon. Net als in een eerder voorbeeld kunnen we proberen een vergelijkbare zoekopdracht te schrijven:

SELECTEER e.phones VAN Medewerker e

Maar dit zal niet helemaal werken zoals we misschien bedoeld hadden. Sinds de geselecteerde vereniging - e.telefoons - is collectie-gewaardeerd, we krijgen een lijst van Verzamelings, in plaats van Telefoon entiteiten:

@Test openbare leegte whenCollectionValuedAssociationIsSpecifiedInSelect_ThenReturnsCollections () {TypedQuery query = entityManager.createQuery ("SELECTEER e.phones VAN werknemer e", Collection.class); Lijst resultList = query.getResultList (); // Beweringen}

Bovendien, als we willen filteren Telefoon entiteiten in WHERE-clausule, staat JPA dat niet toe. Dit is zo omdat een padexpressie kan niet worden voortgezet vanuit een associatie met verzamelingswaarde. Dus bijvoorbeeld e.phones.number is niet geldig.

In plaats daarvan moeten we een expliciete inner join maken en een alias maken voor de Telefoon entiteit. Dan kunnen we de Telefoon entiteit in de SELECT- of WHERE-clausule:

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

4. Outer Join

Als twee of meer entiteiten buitenwaarts zijn verbonden, de records die voldoen aan de join-voorwaarde en ook de records in de linkerentiteit worden verzameld in het resultaat:

@Test public void whenLeftKeywordIsSpecified_thenCreatesOuterJoinAndIncludeNonMatched () {TypedQuery query = entityManager.createQuery ("SELECTEER DISTINCT d VAN Afdeling d LEFT JOIN d.medewerkers e", Afdeling.klasse); Lijst resultList = query.getResultList (); // Beweringen ...}

Hier zal het resultaat bevatten afdelings die zijn geassocieerd Werknemers en ook degenen die er geen hebben.

Dit wordt ook wel een left outer join genoemd. JPA biedt geen juiste joins waar we ook niet-overeenkomende records van de juiste entiteit verzamelen. Hoewel we juiste joins kunnen simuleren door entiteiten in de FROM-component te verwisselen.

5. Sluit zich aan bij de WHERE-clausule

5.1. Met een conditie

We kunnen twee entiteiten vermelden in de FROM-component enspecificeer vervolgens de join-voorwaarde in de WHERE-component.

Dit kan vooral handig zijn als externe sleutels op databaseniveau niet aanwezig zijn:

@Test openbare leegte whenEntitiesAreListedInFromAndMatchedInWhere_ThenCreatesJoin () {TypedQuery query = entityManager.createQuery ("SELECTEER d VAN werknemer e, afdeling d WAAR e.department = d", afdeling.klasse); Lijst resultList = query.getResultList (); // Beweringen ...}

Hier doen we mee Werknemer en afdeling entiteiten, maar deze keer specificeert u een voorwaarde in de WHERE-component.

5.2. Zonder conditie (Cartesiaans product)

Evenzo we kunnen twee entiteiten in de FROM-component vermelden zonder een voorwaarde voor samenvoegen op te geven. In dit geval, we krijgen een Cartesiaans product terug. Dit betekent dat elk record in de eerste entiteit wordt gepaard met elk ander record in de tweede entiteit:

@Test openbare leegte whenEntitiesAreListedInFrom_ThenCreatesCartesianProduct () {TypedQuery query = entityManager.createQuery ("SELECTEER d VAN Werknemer e, Afdeling d", Afdeling.klasse); Lijst resultList = query.getResultList (); // Beweringen ...}

Zoals we kunnen raden, presteren dit soort zoekopdrachten niet goed.

6. Meerdere joins

Tot nu toe hebben we twee entiteiten gebruikt om joins uit te voeren, maar dit is geen regel. We kunnen ook meerdere entiteiten samenvoegen in een enkele JPQL-query:

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

Hier selecteren we alles Telefoons van allemaal Werknemers die een Afdeling. Net als bij andere inner joins, specificeren we geen voorwaarden, aangezien JPA deze informatie extraheert uit metagegevens in kaart brengen.

7. Fetch Joins

Laten we het nu hebben over fetch-joins. Het primaire gebruik ervan is om lui geladen associaties gretig op te halen voor de huidige zoekopdracht.

Hier zullen we gretig laden Werknemers vereniging:

@Test openbare leegte whenFetchKeywordIsSpecified_ThenCreatesFetchJoin () {TypedQuery query = entityManager.createQuery ("SELECTEER d VAN Afdeling d WORD LID VAN FETCH d.werknemers", Afdeling.class); Lijst resultList = query.getResultList (); // Beweringen ...}

Hoewel deze zoekopdracht erg lijkt op andere zoekopdrachten, is er één verschil, en dat is dat de Werknemers worden gretig geladen. Dat betekent dat we eenmaal bellen getResultList in de bovenstaande test, de afdeling entiteiten zullen hun medewerkers veld geladen, waardoor we een nieuwe reis naar de database besparen.

Maar let op de afweging van het geheugen. We zijn misschien efficiënter omdat we maar één query hebben uitgevoerd, maar we hebben ook alle afdelings en hun werknemers in één keer in het geheugen.

We kunnen ook de outer fetch-join uitvoeren op een vergelijkbare manier als de outer joins, waarbij we records verzamelen van de linkerentiteit die niet overeenkomen met de join-voorwaarde. En bovendien laadt het gretig de opgegeven associatie:

@Test public void whenLeftAndFetchKeywordsAreSpecified_ThenCreatesOuterFetchJoin () {TypedQuery query = entityManager.createQuery ("SELECTEER d VAN Afdeling d LEFT JOIN FETCH d.medewerkers", Afdeling.class); Lijst resultList = query.getResultList (); // Beweringen ...}

8. Samenvatting

In dit artikel hebben we JPA-join-typen behandeld.

Zoals altijd kun je alle voorbeelden voor deze en andere tutorials bekijken op GitHub.