Hurtigguide til MapStruct

1. Oversikt

I denne artikkelen vil vi utforske bruken av MapStruct, som ganske enkelt er en Java Bean-kartlegger.

Denne API-en inneholder funksjoner som automatisk kartlegges mellom to Java Beans. Med MapStruct trenger vi bare å lage grensesnittet, og biblioteket oppretter automatisk en konkret implementering i løpet av kompileringstiden.

2. MapStruct og overfør objektmønster

For de fleste applikasjoner vil du legge merke til mye kokerplatekode som konverterer POJOer til andre POJOer.

For eksempel skjer en vanlig type konvertering mellom vedvarestøttede enheter og DTO-er som går ut til klientsiden.

Så det er problemet MapStruct løserå lage bønnekarttere manuelt er tidkrevende. Biblioteket kan generere klasser for bønnemappere automatisk.

3. Maven

La oss legge til avhengigheten nedenfor i vår Maven pom.xml:

 org.mapstruct mapstruct 1.3.1.Final 

Den siste stabile utgivelsen av Mapstruct og hans prosessor er begge tilgjengelige fra Maven Central Repository.

La oss også legge til annotationProcessorPaths delen til konfigurasjonsdelen av maven-compiler-plugin plugg inn.

De mapstruct-prosessor brukes til å generere kartleggerimplementeringen under byggingen:

 org.apache.maven.plugins maven-compiler-plugin 3.5.1 1.8 1.8 org.mapstruct mapstruct-prosessor 1.3.1.Final 

4. Grunnleggende kartlegging

4.1. Opprette en POJO

La oss først lage en enkel Java POJO:

offentlig klasse SimpleSource {private strengnavn; privat strengbeskrivelse; // getters and setters} offentlig klasse SimpleDestination {private strengnavn; privat strengbeskrivelse; // getters og setters}

4.2. Mapper-grensesnittet

@Mapper offentlig grensesnitt SimpleSourceDestinationMapper {SimpleDestination sourceToDestination (SimpleSource kilde); SimpleSource destinationToSource (SimpleDestination destination); }

Legg merke til at vi ikke opprettet en implementeringsklasse for vår SimpleSourceDestinationMapper - fordi MapStruct skaper det for oss.

4.3. The New Mapper

Vi kan utløse MapStruct-behandlingen ved å utføre en mvn ren installasjon.

Dette vil generere implementeringsklassen under / mål / genererte kilder / merknader /.

Her er klassen som MapStruct oppretter automatisk for oss:

offentlig klasse SimpleSourceDestinationMapperImpl implementerer SimpleSourceDestinationMapper {@Override public SimpleDestination sourceToDestination (SimpleSource source) {if (source == null) {return null; } SimpleDestination simpleDestination = ny SimpleDestination (); simpleDestination.setName (source.getName ()); simpleDestination.setDescription (source.getDescription ()); returner enkel destinasjon; } @Override public SimpleSource destinationToSource (SimpleDestination destination) {if (destination == null) {return null; } SimpleSource simpleSource = ny SimpleSource (); simpleSource.setName (destination.getName ()); simpleSource.setDescription (destinasjon.getDescription ()); returner simpleSource; }}

4.4. En prøvesak

Til slutt, med alt generert, la oss skrive en testtilfelle som viser at verdiene i SimpleSource samsvarer med verdier i SimpleDestination.

offentlig klasse SimpleSourceDestinationMapperIntegrationTest {private SimpleSourceDestinationMapper mapper = Mappers.getMapper (SimpleSourceDestinationMapper.class); @Test offentlig ugyldig givenSourceToDestination_whenMaps_thenCorrect () {SimpleSource simpleSource = ny SimpleSource (); simpleSource.setName ("SourceName"); simpleSource.setDescription ("SourceDescription"); SimpleDestination destinasjon = mapper.sourceToDestination (simpleSource); assertEquals (simpleSource.getName (), destination.getName ()); assertEquals (simpleSource.getDescription (), destination.getDescription ()); } @Test offentlig ugyldig givenDestinationToSource_whenMaps_thenCorrect () {SimpleDestination destination = new SimpleDestination (); destination.setName ("Destinasjonsnavn"); destination.setDescription ("DestinationDescription"); SimpleSource kilde = mapper.destinationToSource (destinasjon); assertEquals (destination.getName (), source.getName ()); assertEquals (destination.getDescription (), source.getDescription ()); }}

5. Kartlegging med avhengighetsinjeksjon

Deretter, la oss få en forekomst av en kartlegger i MapStruct ved å bare ringe Mappers.getMapper (YourClass.class).

Selvfølgelig er det en veldig manuell måte å få forekomsten på - et mye bedre alternativ ville være å injisere kartleggeren direkte der vi trenger det (hvis prosjektet vårt bruker en løsning for avhengighetsinjeksjon).

Heldigvis har MapStruct solid støtte for både Spring og CDI (Kontekster og avhengighetsinjeksjon).

For å bruke Spring IoC i kartleggeren vår, må vi legge til componentModeltilskrive @Kartlegger med verdien vår og for CDI ville være cdi .

5.1. Endre kartleggeren

Legg til følgende kode i SimpleSourceDestinationMapper:

@Mapper (componentModel = "spring") offentlig grensesnitt SimpleSourceDestinationMapper

6. Kartlegge felt med forskjellige feltnavn

Fra vårt forrige eksempel klarte MapStruct å kartlegge bønnene våre automatisk fordi de har samme feltnavn. Så hva om en bønne vi skal kartlegge har et annet feltnavn?

For vårt eksempel vil vi lage en ny bønne som heter Ansatt og EmployeeDTO.

6.1. Nye POJOer

offentlig klasse EmployeeDTO {private int employeeId; private String medarbeidernavn; // getters og setters}
offentlig klasse ansatt {privat int id; privat strengnavn; // getters og setters}

6.2. Mapper-grensesnittet

Når vi kartlegger forskjellige feltnavn, må vi konfigurere kildefeltet til målfeltet, og for å gjøre det må vi legge til @Kartinger kommentar. Denne kommentaren godtar en rekke @Kartlegging kommentar som vi vil bruke til å legge til mål- og kildeattributtet.

I MapStruct kan vi også bruke punktnotasjon for å definere et medlem av en bønne:

@Mapper offentlig grensesnitt EmployeeMapper {@Mappings ({@Mapping (target = "employeeId", source = "entity.id"), @Mapping (target = "employeeName", source = "entity.name")}) EmployeeDTO-medarbeiderTo Ansattes enhet); @Mappings ({@Mapping (target = "id", source = "dto.employeeId"), @Mapping (target = "name", source = "dto.employeeName")}) AnsattmedarbeiderDTOtoEmployee (EmployeeDTO dto); }

6.3. Test saken

Igjen må vi teste at både kilde- og målobjektverdiene samsvarer med:

@Test offentlig ugyldighet gittEmployeeDTOwithDiffNametoEmployee_whenMaps_thenCorrect () {EmployeeDTO dto = new EmployeeDTO (); dto.setEmployeeId (1); dto.setEmployeeName ("John"); Ansattes enhet = kartlegger.medarbeiderDTOtoMedarbeider (dto); assertEquals (dto.getEmployeeId (), entity.getId ()); assertEquals (dto.getEmployeeName (), entity.getName ()); }

Flere testsaker finnes i Github-prosjektet.

7. Kartlegging av bønner med barnebønner

Deretter viser vi hvordan du kartlegger en bønne med referanser til andre bønner.

7.1. Endre POJO

La oss legge til en ny bønnehenvisning til Ansatt gjenstand:

offentlig klasse EmployeeDTO {private int employeeId; private String medarbeidernavn; private DivisionDTO divisjon; // getters og setters utelatt}
offentlig klasse ansatt {privat int id; privat strengnavn; privat divisjon divisjon; // getters og setters utelatt}
offentlig klasse Divisjon {privat int id; privat strengnavn; // standardkonstruktør, getters og setters utelatt}

7.2. Endre kartleggeren

Her må vi legge til en metode for å konvertere Inndeling til DivisjonDTO og vice versa; Hvis MapStruct oppdager at objekttypen må konverteres og metoden for å konvertere eksisterer i samme klasse, vil den bruke den automatisk.

La oss legge dette til kartleggeren:

DivisionDTO divisionToDivisionDTO (divisjonsenhet); DivisjonsavdelingDTOtoDivision (DivisionDTO dto);

7.3. Endre testsaken

La oss endre og legge til noen få testtilfeller til den eksisterende:

@Test offentlig ugyldig givenEmpDTONestedMappingToEmp_whenMaps_thenCorrect () {EmployeeDTO dto = new EmployeeDTO (); dto.setDivision (ny DivisionDTO (1, "Division1")); Ansattes enhet = kartlegger.medarbeiderDTOtoMedarbeider (dto); assertEquals (dto.getDivision (). getId (), entity.getDivision (). getId ()); assertEquals (dto.getDivision (). getName (), entity.getDivision (). getName ()); }

8. Kartlegging med typekonvertering

MapStruct tilbyr også et par ferdige implisitte konverteringer, og for vårt eksempel vil vi prøve å konvertere en strengdato til en faktisk Dato gjenstand.

For mer informasjon om implisitt type konvertering, kan du lese MapStruct referanseguide.

8.1. Endre bønnene

Legg til en startdato for den ansatte:

offentlig klasse Ansatt {// andre felt private Dato startDt; // getters og setters}
offentlig klasse EmployeeDTO {// andre felt private StrengmedarbeiderStartDt; // getters og setters}

8.2. Endre kartleggeren

Endre kartleggeren og oppgi dateFormat for vår startdato:

@Mappings ({@Mapping (target = "employeeId", source = "entity.id"), @Mapping (target = "employeeName", source = "entity.name"), @Mapping (target = "employeeStartDt", source) = "entity.startDt", dateFormat = "dd-MM-åååå HH: mm: ss")}) EmployeeDTO-medarbeiderToEmployeeDTO (ansatt enhet); @Mappings ({@Mapping (target = "id", source = "dto.employeeId"), @Mapping (target = "name", source = "dto.employeeName"), @Mapping (target = "startDt", source = "dto.employeeStartDt", dateFormat = "dd-MM-åååå HH: mm: ss")}) Ansattes medarbeiderDTOtoMedarbeider (EmployeeDTO dto);

8.3. Endre testsaken

La oss legge til noen flere testtilfeller for å bekrefte at konverteringen er riktig:

privat statisk sluttstreng DATE_FORMAT = "dd-MM-åååå HH: mm: ss"; @Test offentlig ugyldighet givenEmpStartDtMappingToEmpDTO_whenMaps_thenCorrect () kaster ParseException {ansatt enhet = ny ansatt (); entity.setStartDt (ny dato ()); EmployeeDTO dto = mapper.employeeToEmployeeDTO (enhet); SimpleDateFormat format = nytt SimpleDateFormat (DATE_FORMAT); assertEquals (format.parse (dto.getEmployeeStartDt ()). toString (), entity.getStartDt (). toString ()); } @Test offentlig ugyldighet gittEmpDTOStartDtMappingToEmp_whenMaps_thenCorrect () kaster ParseException {EmployeeDTO dto = new EmployeeDTO (); dto.setEmployeeStartDt ("01-04-2016 01:00:00"); Ansattes enhet = kartlegger.medarbeiderDTOtoMedarbeider (dto); SimpleDateFormat format = nytt SimpleDateFormat (DATE_FORMAT); assertEquals (format.parse (dto.getEmployeeStartDt ()). toString (), entity.getStartDt (). toString ()); }

9. Kartlegging med en abstrakt klasse

Noen ganger kan det være lurt å tilpasse kartleggeren vår på en måte som overgår @Mapping-funksjonene.

For eksempel, i tillegg til typekonvertering, vil vi kanskje transformere verdiene på en eller annen måte som i eksemplet vårt nedenfor.

I slike tilfeller kan vi lage en abstrakt klasse og implementere metoder vi ønsker å ha tilpasset og la abstrakte de som skal genereres av MapStruct.

9.1. Grunnleggende modell

I dette eksemplet bruker vi følgende klasse:

offentlig klasse Transaksjon {privat Lang id; privat streng uuid = UUID.randomUUID (). toString (); privat BigDecimal totalt; // standard getters}

og en matchende DTO:

offentlig klasse TransactionDTO {private String uuid; privat Lang totalinngang; // standard getters og setters}

Den vanskelige delen her er å konvertere BigDecimalTotalmengde dollar til en Lang total INSENT.

9.2. Definere en kartlegger

Vi kan oppnå dette ved å skape vårt Kartlegger som en abstrakt klasse:

@Mapper abstrakt klasse TransactionMapper {public TransactionDTO toTransactionDTO (Transaction transaksjon) {TransactionDTO transactionDTO = new TransactionDTO (); transactionDTO.setUuid (transaction.getUuid ()); transactionDTO.setTotalInCents (transaction.getTotal () .multiply (new BigDecimal ("100")). longValue ()); returtransaksjonDTO; } offentlig abstrakt Liste til TransaksjonDTO (Innsamlingstransaksjoner); }

Her har vi implementert vår fullt tilpassede kartleggingsmetode for en enkelt objektkonvertering.

På den annen side forlot vi metoden som er ment å kartlegge Samlingtil en Listeabstrakt, så MapStruct vil implementere det for oss.

9.3. Genererte resultat

Som vi allerede har implementert metoden for å kartlegge enkelt Transaksjoninn i en TransaksjonDTO, vi forventer Mapstructå bruke den i den andre metoden. Følgende genereres:

@Generated class TransactionMapperImpl utvider TransactionMapper {@Override public List toTransactionDTO (Collection Transactions) {if (transaksjoner == null) {return null; } Listeliste = ny ArrayList (); for (Transaksjonstransaksjon: transaksjoner) {list.add (toTransactionDTO (transaksjon)); } returliste; }}

Som vi kan se i linje 12, MapStruct bruker implementeringen vår i metoden som den genererte.

10. Kommentarer før kartlegging og etter kartlegging

Her er en annen måte å tilpasse på @Kartlegging funksjoner ved å bruke @BeforeMapping og @AfterMapping kommentarer. Merknadene brukes til å markere metoder som påkalles rett før og etter kartleggingslogikken.

De er ganske nyttige i scenarier der vi kanskje vil ha dette atferd som skal brukes på alle kartlagte supertyper.

La oss se på et eksempel som kartlegger undertypene av Bil; Elektrisk bil, og BioDieselCar, til CarDTO.

Under kartlegging vil vi kartlegge forestillingen om typer til Drivstoff type enum-feltet i DTO, og etter at kartleggingen er ferdig, vil vi endre navnet på DTO til store bokstaver.

10.1. Grunnleggende modell

I dette eksemplet bruker vi følgende klasser:

offentlig klasse bil {privat int id; privat strengnavn; }

Undertyper av Bil:

offentlig klasse BioDieselCar utvider bilen {}
offentlig klasse ElectricCar utvider bilen {}

De CarDTO med enum-felttype Drivstoff type:

offentlig klasse CarDTO {privat int id; privat strengnavn; private FuelType fuelType; }
offentlig enum FuelType {ELECTRIC, BIO_DIESEL}

10.2. Definere kartleggeren

La oss nå fortsette og skrive vår abstrakte kartleggerklasse, som kartlegger Bil til CarDTO:

@Mapper offentlig abstrakt klasse CarsMapper {@BeforeMapping beskyttet ugyldig enrichDTOWithFuelType (Car car, @MappingTarget CarDTO carDto) {if (bilforekomst av ElectricCar) {carDto.setFuelType (FuelType.ELECTRIC); } hvis (bilforekomst av BioDieselCar) {carDto.setFuelType (FuelType.BIO_DIESEL); }} @AfterMapping beskyttet ugyldig convertNameToUpperCase (@MappingTarget CarDTO carDto) {carDto.setName (carDto.getName (). ToUpperCase ()); } offentlig abstrakt CarDTO toCarDto (Car car); }

@MappingTarget er en parameterkommentar som fyller målkartleggingen DTO rett før kartleggingslogikken utføresi tilfelle @BeforeMapping og rett etter i tilfelle @AfterMapping kommentert metode.

10.3. Resultat

De CarsMapper definert ovenfor generererdegjennomføring:

@Generated public class CarsMapperImpl utvider CarsMapper {@Override public CarDTO toCarDto (Car car) {if (car == null) {return null; } CarDTO carDTO = ny CarDTO (); richDTOWithFuelType (bil, carDTO); carDTO.setId (car.getId ()); carDTO.setName (car.getName ()); convertNameToUpperCase (carDTO); retur bilDTO; }}

Legg merke til hvordan de merkede metodene påkallingene omgir kartleggingslogikken i implementeringen.

11. Støtte for Lombok

I den siste versjonen av MapStruct ble Lombok-støtte kunngjort. Så vi kan enkelt kartlegge en kildeenhet og en destinasjon ved hjelp av Lombok.

For å aktivere Lombok-støtte, må vi legge til avhengigheten i kommentarprosessoren. Så nå har vi mapstruct-prosessoren så vel som Lombok i Maven compiler plugin:

 org.apache.maven.plugins maven-compiler-plugin 3.5.1 1.8 1.8 org.mapstruct mapstruct-prosessor 1.3.1.Final org.projectlombok lombok 1.18.4 

La oss definere kildeenheten ved hjelp av Lombok-merknader:

@Getter @Setter offentlig klasse bil {privat int id; privat strengnavn; }

Og destinasjonsdataoverføringsobjektet:

@Getter @Setter offentlig klasse CarDTO {privat int id; privat strengnavn; }

Kartleggergrensesnittet for dette forblir likt vårt forrige eksempel:

@Mapper offentlig grensesnitt CarMapper {CarMapper INSTANCE = Mappers.getMapper (CarMapper.class); CarDTO carToCarDTO (Car car); }

12. Støtte for standarduttrykk

Starter med versjon 1.3.0, vi kan bruke standarduttrykk attributt til @Kartlegging merknad for å spesifisere et uttrykk som bestemmer verdien til destinasjonsfeltet hvis kildefeltet er null. Dette kommer i tillegg til det eksisterende standardverdi attributtfunksjonalitet.

Kilden enhet:

offentlig klasse Person {privat int id; privat strengnavn; }

Destinasjonsdataoverføringsobjektet:

offentlig klasse PersonDTO {privat int id; privat strengnavn; }

Hvis den id felt for kildeenheten er null, vi vil generere et tilfeldig id og tilordne den til destinasjonen og holde andre eiendomsverdier som de er:

@Mapper offentlig grensesnitt PersonMapper {PersonMapper INSTANCE = Mappers.getMapper (PersonMapper.class); @Mapping (target = "id", source = "person.id", defaultExpression = "java (java.util.UUID.randomUUID (). ToString ())") PersonDTO personToPersonDTO (Person person); }

La oss legge til en testtilfelle for å verifisere eksekvering av uttrykk:

@Test offentlig ugyldighet givenPersonEntitytoPersonWithExpression_whenMaps_thenCorrect () Personenhet = ny person (); entity.setName ("Micheal"); PersonDTO personDto = PersonMapper.INSTANCE.personToPersonDTO (enhet); assertNull (entity.getId ()); assertNotNull (personDto.getId ()); assertEquals (personDto.getName (), entity.getName ()); }

13. Konklusjon

Denne artikkelen ga en introduksjon til MapStruct. Vi har introdusert det meste av det grunnleggende i Mapping-biblioteket og hvordan du bruker det i applikasjonene våre.

Implementeringen av disse eksemplene og testene finner du i Github-prosjektet. Dette er et Maven-prosjekt, så det skal være enkelt å importere og kjøre som det er.


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