Vårdata JPA-projeksjoner

1. Oversikt

Når du bruker Spring Data JPA til å implementere utholdenhetslaget, returnerer depotet vanligvis en eller flere forekomster av rotklassen. Imidlertid trenger vi oftere enn ikke alle egenskapene til de returnerte objektene.

I slike tilfeller kan det være ønskelig å hente data som objekter av tilpassede typer. Disse typene gjenspeiler delvis visninger av rotklassen, og inneholder bare egenskaper vi bryr oss om. Det er her anslag kommer til nytte.

2. Første oppsett

Det første trinnet er å sette opp prosjektet og fylle ut databasen.

2.1. Maven avhengigheter

For avhengigheter, vennligst sjekk avsnitt 2 i denne opplæringen.

2.2. Enhetsklasser

La oss definere to enhetsklasser:

@Entity offentlig klasse Adresse {@Id privat Lang id; @OneToOne privatperson person; privat strengstat; private String city; privat String street; privat streng zipCode; // getters og setters}

Og:

@Entity offentlig klasse Person {@Id privat Lang id; privat streng fornavn; privat streng etternavn; @OneToOne (mappedBy = "person") privat adresse adresse; // getters og setters}

Forholdet mellom Person og Adresse enheter er toveis en-til-en: Adresse er eiersiden, og Person er den omvendte siden.

Legg merke til i denne opplæringen, vi bruker en innebygd database - H2.

Når en innebygd database er konfigurert, genererer Spring Boot automatisk underliggende tabeller for enhetene vi definerte.

2.3. SQL-skript

Vi bruker projection-insert-data.sql skript for å fylle begge støttetabellene:

INSERT INTO person (id, first_name, last_name) VALUES (1, 'John', 'Doe'); INSERT IN address (id, person_id, state, city, street, postnummer) VERDIER (1,1, 'CA', 'Los Angeles', 'Standford Ave', '90001');

For å rydde opp i databasen etter hver testkjøring, kan vi bruke et annet skript, kalt projection-clean-up-data.sql:

SLETT FRA adressen; SLETT FRA personen;

2.4. Testklasse

For å bekrefte at fremskrivninger gir riktige data, trenger vi en testklasse:

@DataJpaTest @RunWith (SpringRunner.class) @Sql (scripts = "/projection-insert-data.sql") @Sql (scripts = "/projection-clean-up-data.sql", executionPhase = AFTER_TEST_METHOD) offentlig klasse JpaProjectionIntegration {// injiserte felt og testmetoder}

Med de gitte kommentarene, Spring Boot oppretter databasen, injiserer avhengigheter og fyller ut og rydder opp tabeller før og etter utførelsen av hver testmetode.

3. Grensesnittbaserte projeksjoner

Når du projiserer en enhet, er det naturlig å stole på et grensesnitt, da vi ikke trenger å gi en implementering.

3.1. Lukkede anslag

Ser tilbake på Adresse klasse, kan vi se den har mange egenskaper, men ikke alle er nyttige. Noen ganger er for eksempel et postnummer nok til å indikere en adresse.

La oss erklære et projeksjonsgrensesnitt for Adresse klasse:

offentlig grensesnitt AddressView {String getZipCode (); }

Bruk den deretter i et lagringsgrensesnitt:

offentlig grensesnitt AddressRepository utvider Repository {List getAddressByState (strengtilstand); }

Det er lett å se at å definere en depotmetode med et projiseringsgrensesnitt er omtrent det samme som med en enhetsklasse.

Den eneste forskjellen er at projeksjonsgrensesnittet, i stedet for enhetsklassen, brukes som elementtype i den returnerte samlingen.

La oss gjøre en rask test av Adresse projeksjon:

@Autowired privat AddressRepository addressRepository; @Test offentlig ugyldig nårUsingClosedProjections_thenViewWithRequiredPropertiesIsReturned () {AddressView addressView = addressRepository.getAddressByState ("CA"). Get (0); assertThat (addressView.getZipCode ()). isEqualTo ("90001"); // ...}

Bak scenen, Spring oppretter en proxy-forekomst av projeksjonsgrensesnittet for hvert objektobjekt, og alle anrop til proxyen blir videresendt til det objektet.

Vi kan bruke projeksjoner rekursivt. For eksempel, her er et projiseringsgrensesnitt for Person klasse:

offentlig grensesnitt PersonView {String getFirstName (); Streng getLastName (); }

La oss nå legge til en metode med returtypen PersonView - en nestet projeksjon - i Adresse projeksjon:

offentlig grensesnitt AddressView {// ... PersonView getPerson (); }

Legg merke til metoden som returnerer den nestede projeksjonen, må ha samme navn som metoden i rotklassen som returnerer den relaterte enheten.

La oss verifisere nestede projeksjoner ved å legge til noen utsagn til testmetoden vi nettopp har skrevet:

// ... PersonView personView = addressView.getPerson (); assertThat (personView.getFirstName ()). isEqualTo ("John"); assertThat (personView.getLastName ()). isEqualTo ("Doe");

Noter det rekursive projeksjoner fungerer bare hvis vi krysser fra eiersiden til den omvendte siden. Skulle vi gjøre det omvendt, ville den nestede projeksjonen være satt til null.

3.2. Åpne projeksjoner

Fram til dette har vi gått gjennom lukkede projeksjoner, som indikerer projeksjonsgrensesnitt hvis metoder nøyaktig samsvarer med navnene på enhetsegenskapene.

Det er en annen slags grensesnittbaserte anslag: åpne projeksjoner. Disse anslagene gjør det mulig for oss å definere grensesnittmetoder med uovertruffen navn og med returverdier beregnet ved kjøretid.

La oss gå tilbake til Person projeksjonsgrensesnitt og legg til en ny metode:

offentlig grensesnitt PersonView {// ... @Value ("# {target.firstName + '' + target.lastName}") String getFullName (); }

Argumentet til @Verdi kommentar er et SPEL-uttrykk der mål designator angir objektet for støttenhet.

Nå skal vi definere et annet depotgrensesnitt:

offentlig grensesnitt PersonRepository utvider repository {PersonView findByLastName (strengnavn); }

For å gjøre det enkelt returnerer vi bare ett enkelt projeksjonsobjekt i stedet for en samling.

Denne testen bekrefter åpne anslag som forventet:

@Autowired private PersonRepository personRepository; @Testpublic ugyldig nårUsingOpenProjections_thenViewWithRequiredPropertiesIsReturned () {PersonView personView = personRepository.findByLastName ("Doe"); assertThat (personView.getFullName ()). isEqualTo ("John Doe"); }

Åpne anslag har en ulempe: Spring Data kan ikke optimalisere spørringskjøring, da de ikke vet på forhånd hvilke egenskaper som skal brukes. Og dermed, vi bør bare bruke åpne anslag når lukkede anslag ikke er i stand til å håndtere våre krav.

4. Klassebaserte fremskrivninger

I stedet for å bruke fullmakter oppretter Spring Data for oss fra projeksjonsgrensesnitt, vi kan definere våre egne projeksjonsklasser.

For eksempel, her er en projeksjonsklasse for Person enhet:

offentlig klasse PersonDto {privat streng fornavn; privat streng etternavn; offentlig PersonDto (streng fornavn, streng etternavn) {this.firstName = fornavn; this.lastName = etternavn; } // getters, lik og hashCode}

For at en projeksjonsklasse skal fungere sammen med et lagringsgrensesnitt, må parameternavnene til konstruktøren matche egenskapene til rotenhetsklassen.

Vi må også definere er lik og hashCode implementeringer - de lar Spring Data behandle projiseringsobjekter i en samling.

La oss nå legge til en metode for Person oppbevaringssted:

offentlig grensesnitt PersonRepository utvider repository {// ... PersonDto findByFirstName (String firstName); }

Denne testen bekrefter vår klassebaserte projeksjon:

@Test offentlig ugyldig nårUsingClassBasedProjections_thenDtoWithRequiredPropertiesIsReturned () {PersonDto personDto = personRepository.findByFirstName ("John"); assertThat (personDto.getFirstName ()). isEqualTo ("John"); assertThat (personDto.getLastName ()). isEqualTo ("Doe"); }

Legg merke til med den klassebaserte tilnærmingen, vi kan ikke bruke nestede projeksjoner.

5. Dynamiske projeksjoner

En enhetsklasse kan ha mange anslag. I noen tilfeller kan vi bruke en bestemt type, men i andre tilfeller kan det hende vi trenger en annen type. Noen ganger trenger vi også å bruke selve enhetsklassen.

Å definere separate repositorgrensesnitt eller metoder bare for å støtte flere returtyper er tungvint. For å takle dette problemet gir Spring Data en bedre løsning: dynamiske projeksjoner.

Vi kan bruke dynamiske anslag bare ved å erklære en lagringsmetode med en Klasse parameter:

offentlig grensesnitt PersonRepository utvider repository {// ... T findByLastName (strengnavn, klassetype); }

Ved å overføre en projeksjonstype eller enhetsklassen til en slik metode, kan vi hente et objekt av ønsket type:

@Test offentlig ugyldig nårUsingDynamicProjections_thenObjectWithRequiredPropertiesIsReturned () {Person person = personRepository.findByLastName ("Doe", Person.class); PersonView personView = personRepository.findByLastName ("Doe", PersonView.class); PersonDto personDto = personRepository.findByLastName ("Doe", PersonDto.class); assertThat (person.getFirstName ()). er EqualTo ("John"); assertThat (personView.getFirstName ()). isEqualTo ("John"); assertThat (personDto.getFirstName ()). isEqualTo ("John"); }

6. Konklusjon

I denne artikkelen gikk vi over forskjellige typer Spring Data JPA-projeksjoner.

Kildekoden for denne opplæringen er tilgjengelig på GitHub. Dette er et Maven-prosjekt og skal kunne kjøre som det er.