Arbejde med Lazy Element Collections i JPA
Jeg har lige annonceret det nye Lær foråret kursus med fokus på det grundlæggende i Spring 5 og Spring Boot 2:
>> KONTROLLER KURSEN1. Oversigt
JPA-specifikationen giver to forskellige hentestrategier: ivrig og doven. Mens den dovne tilgang hjælper med at undgå unødvendigt at indlæse data, som vi ikke har brug for, vi har undertiden brug for at læse data, der ikke oprindeligt blev indlæst i en lukket Persistence-kontekst. Desuden er adgang til dovne elementsamlinger i en lukket Persistence Context et almindeligt problem.
I denne vejledning vil vi fokusere på, hvordan man indlæser data fra dovne elementsamlinger. Vi undersøger tre forskellige løsninger: en involverer JPA-forespørgselssprog, en anden med brug af enhedsgrafer og den sidste med transaktionsformidling.
2. Elementindsamlingsproblemet
Som standard bruger JPA lazy fetch-strategien i tilknytninger af typen @ElementCollection. Enhver adgang til samlingen i en lukket Persistence-kontekst vil således resultere i en undtagelse.
For at forstå problemet, lad os definere en domænemodel baseret på forholdet mellem medarbejderen og dens telefonliste:
@Entity offentlig klassemedarbejder {@Id privat int id; privat strengnavn; @ElementCollection @CollectionTable (navn = "medarbejder_telefon", joinColumns = @JoinColumn (navn = "medarbejder_id")) private Liste-telefoner; // standardkonstruktører, getters og settere} @ Embeddable public class Telefon {privat String type; private String areaCode; privat streng nummer; // standardkonstruktører, getters og settere}
Vores model specificerer, at en medarbejder kan have mange telefoner. Telefonlisten er en samling af indlejrede typer. Lad os bruge et Spring Repository med denne model:
@Repository public class EmployeeRepository {public Employee findById (int id) {return em.find (Employee.class, id); } // yderligere egenskaber og hjælpemetoder}
Lad os nu gengive problemet med en simpel JUnit test case:
offentlig klasse ElementCollectionIntegrationTest {@Fore public void init () {Medarbejdermedarbejder = ny medarbejder (1, "Fred"); medarbejder.setPhones (Arrays.asList (ny telefon ("arbejde", "+55", "99999-9999"), ny telefon ("hjem", "+55", "98888-8888"))); workerRepository.save (medarbejder); } @ Efter offentlig annullering ren () {medarbejderRepository.remove (1); } @Test (forventet = org.hibernate.LazyInitializationException.class) offentlig ugyldig nårAccessLazyCollection_thenThrowLazyInitializationException () {Medarbejdermedarbejder = medarbejderRepository.findById (1); assertThat (medarbejder.getPhones (). størrelse (), er (2)); }}
Denne test kaster en undtagelse, når vi prøver at få adgang til telefonlisten, fordi Persistence Context er lukket.
Vi kan løse dette problem ved at ændring af hentningsstrategien for @ElementCollection at bruge den ivrige tilgang. Imidlertid henter data ivrigt er ikke nødvendigvis den bedste løsning, da telefondataene altid indlæses, uanset om vi har brug for det eller ej.
3. Indlæser data med JPA Query Language
JPA-forespørgselssprog giver os mulighed for at tilpasse de projicerede oplysninger. Derfor kan vi definere en ny metode i vores Medarbejderopbevaring for at vælge medarbejderen og dens telefoner:
offentlig medarbejder findByJPQL (int id) {returner em.createQuery ("VÆLG u FRA medarbejder SOM du BLIVER MED FETCH u.phones WHERE u.id =: id", Employee.class) .setParameter ("id", id) .getSingleResult ( ); }
Ovenstående forespørgsel bruger en indre sammenkædningsoperation til at hente telefonlisten for hver medarbejder, der returneres.
4. Indlæsning af data med enhedsgraf
En anden mulig løsning er at bruge enhedsgraffunktionen fra JPA. Enhedsgrafen gør det muligt for os at vælge, hvilke felter der skal projiceres af JPA-forespørgsler. Lad os definere endnu en metode i vores lager:
offentlig medarbejder findByEntityGraph (int id) {EntityGraph entityGraph = em.createEntityGraph (Employee.class); entityGraph.addAttributeNodes ("navn", "telefoner"); Kortegenskaber = nyt HashMap (); egenskaber.put ("javax.persistence.fetchgraph", entityGraph); returner em.find (Medarbejder.klasse, id, ejendomme); }
Det kan vi se vores enhedsgraf indeholder to attributter: navn og telefoner. Så når JPA oversætter dette til SQL, projicerer det de relaterede kolonner.
5. Indlæsning af data i et transaktionelt omfang
Endelig skal vi undersøge en sidste løsning. Indtil videre har vi set, at problemet er relateret til livscyklussen for Persistence Context.
Hvad der sker er det vores Persistence Context er transaktionsomfanget og forbliver åben, indtil transaktionen er afsluttet. Transaktionens livscyklus spænder fra begyndelsen til slutningen af udførelsen af arkivmetoden.
Så lad os oprette en anden testcase og konfigurere vores Persistence Context til at binde til en transaktion startet med vores testmetode. Vi holder Persistence Context åben indtil testen slutter:
@ Test @ Transactional public void whenUseTransaction_thenFetchResult () {Medarbejdermedarbejder = medarbejderRepository.findById (1); assertThat (medarbejder.getPhones (). størrelse (), er (2)); }
Det @Transaktionel annotation konfigurerer en transaktionsproxy omkring forekomsten af den relaterede testklasse. Desuden er transaktionen forbundet med den tråd, der udfører den. I betragtning af standardindstillingen for transaktionsformidling slutter hver Persistence Context, der oprettes ud fra denne metode, til den samme transaktion. Følgelig er transaktionens vedvarende kontekst bundet til transaktionens omfang af testmetoden.
6. Konklusion
I denne vejledning vi evaluerede tre forskellige løsninger til at løse problemet med læsning af data fra dovne foreninger i en lukket Persistence-kontekst.
Først brugte vi JPA-forespørgselssproget til at hente elementets samlinger. Dernæst definerede vi en enhedsgraf for at hente de nødvendige data.
Og i den ultimative løsning brugte vi Spring Transaction til at holde Persistence Context åben og læse de nødvendige data.
Som altid er eksempelkoden til denne vejledning tilgængelig på GitHub.
Java bund