Jobber med Lazy Element Collections i JPA

Java Top

Jeg kunngjorde nettopp det nye Lær våren kurs, med fokus på det grunnleggende i vår 5 og vårstøvel 2:

>> KONTROLLER KURSET

1. Oversikt

JPA-spesifikasjonen gir to forskjellige hentestrategier: ivrig og lat. Mens lat tilnærming hjelper til med å unngå unødvendig lasting av data som vi ikke trenger, noen ganger trenger vi å lese data som ikke ble lastet inn i en lukket Persistence-kontekst. Dessuten er tilgang til late elementsamlinger i en lukket Persistence Context et vanlig problem.

I denne opplæringen vil vi fokusere på hvordan du laster inn data fra dovne elementsamlinger. Vi vil utforske tre forskjellige løsninger: en som involverer JPA-spørringsspråket, en annen med bruk av enhetsdiagrammer og den siste med transaksjonsforplantning.

2. Elementsamlingsproblemet

Som standard bruker JPA strategien for lat henting i assosiasjoner av typen @ElementCollection. Dermed vil enhver tilgang til samlingen i en lukket Persistence Context resultere i et unntak.

For å forstå problemet, la oss definere en domenemodell basert på forholdet mellom den ansatte og telefonlisten:

@Entity offentlig klasse ansatt {@Id privat int id; privat strengnavn; @ElementCollection @CollectionTable (navn = "ansatt_telefon", joinColumns = @JoinColumn (navn = "ansatt_id")) private Liste telefoner; // standardkonstruktører, getters og settere} @ Embeddable public class Phone {private String type; private String areaCode; privat strengnummer; // standardkonstruktører, getters og setters}

Modellen vår spesifiserer at en ansatt kan ha mange telefoner. Telefonlisten er en samling av innebygde typer. La oss bruke et Spring Repository med denne modellen:

@Repository public class EmployeeRepository {public Employee findById (int id) {return em.find (Employee.class, id); } // tilleggsegenskaper og hjelpemetoder} 

La oss gjengi problemet med en enkel JUnit-testtilfelle:

offentlig klasse ElementCollectionIntegrationTest {@Før offentlig ugyldig init () {Ansatt ansatt = ny ansatt (1, "Fred"); ansatte.setPhones (Arrays.asList (ny telefon ("arbeid", "+55", "99999-9999"), ny telefon ("hjem", "+55", "98888-8888"))); employeeRepository.save (ansatt); } @Efter offentlig ugyldig rengjøring () {ansatteRepository.remove (1); } @Test (forventet = org.hibernate.LazyInitializationException.class) offentlig ugyldig nårAccessLazyCollection_thenThrowLazyInitializationException () {Ansatt ansatt = ansattRepository.findById (1); assertThat (medarbeider.getPhones (). størrelse (), er (2)); }} 

Denne testen kaster et unntak når vi prøver å få tilgang til telefonlisten fordi Persistence Context er stengt.

Vi kan løse dette problemet ved å endre hentestrategien til @ElementCollection å bruke den ivrige tilnærmingen. Imidlertid henter dataene ivrig er ikke nødvendigvis den beste løsningen, siden telefondataene alltid vil lastes inn, enten vi trenger det eller ikke.

3. Laste inn data med JPA Query Language

JPA-spørrespråket lar oss tilpasse den projiserte informasjonen. Derfor kan vi definere en ny metode i vår Ansattes depot for å velge den ansatte og dens telefoner:

offentlig ansatt findByJPQL (int id) {return em.createQuery ("VELG deg FRA medarbeider SOM BLI MEDLEM FETCH u.phones WHERE u.id =: id", Employee.class) .setParameter ("id", id) .getSingleResult ( ); } 

Ovenstående spørsmål bruker en indre sammenkoblingsoperasjon for å hente telefonlisten for hver ansatt som returneres.

4. Laste inn data med enhetsgraf

En annen mulig løsning er å bruke enhetsgraffunksjonen fra JPA. Enhetsgrafen gjør det mulig for oss å velge hvilke felt som skal projiseres av JPA-spørsmål. La oss definere en annen metode i depotet vårt:

offentlig ansatt findByEntityGraph (int id) {EntityGraph entityGraph = em.createEntityGraph (Employee.class); entityGraph.addAttributeNodes ("navn", "telefoner"); Kartegenskaper = nytt HashMap (); properties.put ("javax.persistence.fetchgraph", entityGraph); returner em.find (ansatt.klasse, id, eiendommer); } 

Vi kan se det enhetsgrafen vår inneholder to attributter: navn og telefoner. Så når JPA oversetter dette til SQL, vil det projisere de relaterte kolonnene.

5. Laste inn data i et transaksjonsomfang

Til slutt skal vi utforske en siste løsning. Så langt har vi sett at problemet er relatert til livssyklusen til Persistence Context.

Det som skjer er at vår Persistence Context er transaksjonsomfang og vil være åpen til transaksjonen er ferdig. Transaksjonens livssyklus strekker seg fra begynnelsen til slutten av kjøringen av depotmetoden.

Så la oss lage en annen testtilfelle og konfigurere Persistence Context til å binde seg til en transaksjon startet med testmetoden vår. Vi holder Persistence Context åpen til testen avsluttes:

@Test @Transactional public void whenUseTransaction_thenFetchResult () {Ansatt ansatt = ansattRepository.findById (1); assertThat (medarbeider.getPhones (). størrelse (), er (2)); } 

De @Transaksjonell kommentar konfigurerer en transaksjonell proxy rundt forekomsten av den relaterte testklassen. Videre er transaksjonen knyttet til tråden som utfører den. Med tanke på standardinnstillingen for transaksjonsforplantning, blir hver Persistence Context opprettet fra denne metoden knyttet til den samme transaksjonen. Følgelig er transaksjonens vedvarende kontekst bundet til transaksjonsomfanget til testmetoden.

6. Konklusjon

I denne veiledningen, Vi evaluerte tre forskjellige løsninger for å løse problemet med å lese data fra late assosiasjoner i en lukket Persistence Context.

Først brukte vi JPA-spørringsspråket for å hente elementsamlingene. Deretter definerte vi en enhetsgraf for å hente de nødvendige dataene.

Og i den ultimate løsningen brukte vi vårtransaksjonen for å holde Persistence Context åpen og lese de nødvendige dataene.

Som alltid er eksempelkoden for denne opplæringen tilgjengelig på GitHub.

Java bunn

Jeg kunngjorde nettopp det nye Lær våren kurs, med fokus på det grunnleggende i vår 5 og vårstøvel 2:

>> KONTROLLER KURSET

$config[zx-auto] not found$config[zx-overlay] not found