Werken met Lazy Element Collections in JPA

Java Top

Ik heb zojuist het nieuwe aangekondigd Leer de lente natuurlijk, gericht op de basisprincipes van Spring 5 en Spring Boot 2:

>> BEKIJK DE CURSUS

1. Overzicht

De JPA-specificatie biedt twee verschillende ophaalstrategieën: gretig en lui. Hoewel de luie benadering helpt voorkomen dat gegevens onnodig worden geladen die we niet nodig hebben, we moeten soms gegevens lezen die in eerste instantie niet zijn geladen in een gesloten persistentiecontext. Bovendien is het een veelvoorkomend probleem om toegang te krijgen tot verzamelingen van luie elementen in een gesloten persistentiecontext.

In deze zelfstudie zullen we ons concentreren op het laden van gegevens uit verzamelingen van luie elementen. We zullen drie verschillende oplossingen onderzoeken: een met de JPA-querytaal, een andere met het gebruik van entiteitsgrafieken en de laatste met transactie-propagatie.

2. Het elementverzamelingsprobleem

Standaard gebruikt JPA de lazy fetch-strategie in associaties van type @ElementCollection. Elke toegang tot de collectie in een gesloten persistentiecontext zal dus resulteren in een uitzondering.

Laten we, om het probleem te begrijpen, een domeinmodel definiëren op basis van de relatie tussen de werknemer en zijn telefoonlijst:

@Entity openbare klasse Medewerker {@Id privé int id; private String naam; @ElementCollection @CollectionTable (name = "employee_phone", joinColumns = @JoinColumn (name = "employee_id")) privélijsttelefoons; // standard constructors, getters en setters} @Embeddable public class Phone {private String type; private String areaCode; privé String-nummer; // standaard constructeurs, getters en setters}

Ons model geeft aan dat een medewerker veel telefoons kan hebben. De telefoonlijst is een verzameling van insluitbare typen. Laten we een Spring Repository gebruiken met dit model:

@Repository openbare klasse EmployeeRepository {openbare werknemer findById (int id) {terugkeer em.find (Employee.class, id); } // aanvullende eigenschappen en hulpmethoden} 

Laten we nu het probleem reproduceren met een eenvoudige JUnit-testcase:

openbare klasse ElementCollectionIntegrationTest {@Before public void init () {Werknemer werknemer = nieuwe werknemer (1, "Fred"); employee.setPhones (Arrays.asList (nieuwe telefoon ("werk", "+55", "99999-9999"), nieuwe telefoon ("thuis", "+55", "98888-8888"))); employeeRepository.save (medewerker); } @After public void clean () {employeeRepository.remove (1); } @Test (verwacht = org.hibernate.LazyInitializationException.class) openbare leegte whenAccessLazyCollection_thenThrowLazyInitializationException () {Werknemer werknemer = werknemerRepository.findById (1); assertThat (employee.getPhones (). size (), is (2)); }} 

Deze test genereert een uitzondering wanneer we proberen toegang te krijgen tot de telefoonlijst omdat de Persistence Context is gesloten.

We kunnen dit probleem oplossen door het wijzigen van de ophaalstrategie van het @ElementCollection om de gretige benadering te gebruiken. De gegevens echter gretig ophalen is niet per se de beste oplossing, aangezien de telefoongegevens altijd worden geladen, of we deze nu nodig hebben of niet.

3. Gegevens laden met JPA Query Language

De JPA-querytaal stelt ons in staat om de geprojecteerde informatie aan te passen. Daarom kunnen we een nieuwe methode definiëren in onze WerknemerRepository om de werknemer en zijn telefoons te selecteren:

openbare werknemer findByJPQL (int id) {return em.createQuery ("SELECTEER u UIT werknemer AS u WORD LID VAN FETCH u.phones WAAR u.id =: id", Employee.class) .setParameter ("id", id) .getSingleResult ( ); } 

De bovenstaande vraag gebruikt een inner join-bewerking om de telefoonlijst op te halen voor elke teruggekeerde werknemer.

4. Gegevens laden met entiteitsgrafiek

Een andere mogelijke oplossing is om de entiteitsgrafiekfunctie van JPA te gebruiken. De entiteitsgrafiek stelt ons in staat om te kiezen welke velden worden geprojecteerd door JPA-query's. Laten we nog een methode definiëren in onze repository:

openbare werknemer findByEntityGraph (int id) {EntityGraph entityGraph = em.createEntityGraph (Employee.class); entiteitGraph.addAttributeNodes ("naam", "telefoons"); Kaarteigenschappen = nieuwe HashMap (); properties.put ("javax.persistence.fetchgraph", entityGraph); return em.find (Employee.class, id, eigenschappen); } 

Dat kunnen we zien onze entiteitsgrafiek bevat twee attributen: naam en telefoons. Dus wanneer JPA dit vertaalt naar SQL, projecteert het de gerelateerde kolommen.

5. Gegevens laden in een transactiebereik

Ten slotte gaan we nog een laatste oplossing onderzoeken. Tot nu toe hebben we gezien dat het probleem verband houdt met de Persistence Context-levenscyclus.

Wat er gebeurt, is dat onze Persistence Context is transactiebereik en blijft open totdat de transactie is voltooid. De transactielevenscyclus strekt zich uit van het begin tot het einde van de uitvoering van de repository-methode.

Laten we dus nog een testcase maken en onze persistentiecontext configureren om te binden aan een transactie die is gestart door onze testmethode. We houden de persistentiecontext open totdat de test eindigt:

@Test @Transactional public void whenUseTransaction_thenFetchResult () {Employee employee = employeeRepository.findById (1); assertThat (employee.getPhones (). size (), is (2)); } 

De @Transactional annotation configureert een transactionele proxy rond de instantie van de gerelateerde testklasse. Bovendien is de transactie gekoppeld aan de thread die deze uitvoert. Rekening houdend met de standaardinstelling voor het doorgeven van transacties, wordt elke persistentiecontext die met deze methode is gemaakt, toegevoegd aan dezelfde transactie. Bijgevolg is de context van de transactiepersistentie gebonden aan de transactieomvang van de testmethode.

6. Conclusie

In deze tutorial we hebben drie verschillende oplossingen geëvalueerd om het probleem van het lezen van gegevens van luie associaties in een gesloten persistentiecontext aan te pakken.

Ten eerste hebben we de JPA-querytaal gebruikt om de elementverzamelingen op te halen. Vervolgens hebben we een entiteitsgrafiek gedefinieerd om de benodigde gegevens op te halen.

En in de ultieme oplossing hebben we de Spring Transaction gebruikt om de Persistence Context open te houden en de benodigde gegevens te lezen.

Zoals altijd is de voorbeeldcode voor deze tutorial beschikbaar op GitHub.

Java onderkant

Ik heb zojuist het nieuwe aangekondigd Leer de lente natuurlijk, gericht op de basisprincipes van Spring 5 en Spring Boot 2:

>> BEKIJK DE CURSUS