Hurtig vejledning til MapStruct

1. Oversigt

I denne artikel vil vi undersøge brugen af ​​MapStruct, som ganske enkelt er en Java Bean-kortlægger.

Denne API indeholder funktioner, der automatisk kortlægger mellem to Java Beans. Med MapStruct behøver vi kun at oprette grænsefladen, og biblioteket opretter automatisk en konkret implementering i løbet af kompileringstiden.

2. MapStruct og overfør objektmønster

I de fleste applikationer bemærker du en masse kedelpladekode, der konverterer POJO'er til andre POJO'er.

For eksempel sker en almindelig type konvertering mellem vedvarende-støttede enheder og DTO'er, der går ud til klientsiden.

Så det er det problem, MapStruct løsermanuel oprettelse af bønnekartnere er tidskrævende. Biblioteket kan generere bønnekortklasser automatisk.

3. Maven

Lad os tilføje nedenstående afhængighed i vores Maven pom.xml:

 org.mapstruct mapstruct 1.3.1.Final 

Den seneste stabile udgivelse af Mapstruct og hans processor er begge tilgængelige fra Maven Central Repository.

Lad os også tilføje annotationProcessorPaths sektion til konfigurationsdelen af maven-compiler-plugin plugin.

Det mapstruct-processor bruges til at generere kortlægning implementering under build:

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

4. Grundlæggende kortlægning

4.1. Oprettelse af en POJO

Lad os først oprette en simpel Java POJO:

offentlig klasse SimpleSource {privat strengnavn; privat streng beskrivelse; // getters and setters} public class SimpleDestination {private String name; privat streng beskrivelse; // getters og setters}

4.2. Mapper-grænsefladen

@Mapper offentlig grænseflade SimpleSourceDestinationMapper {SimpleDestination sourceToDestination (SimpleSource source); SimpleSource destinationToSource (SimpleDestination destination); }

Bemærk, at vi ikke oprettede en implementeringsklasse til vores SimpleSourceDestinationMapper - fordi MapStruct skaber det for os.

4.3. Den nye kortlægger

Vi kan udløse MapStruct-behandlingen ved at udføre en mvn ren installation.

Dette genererer implementeringsklassen under / mål / genererede kilder / annoteringer /.

Her er den klasse, som MapStruct automatisk opretter for os:

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 ()); returnere simpleDestination; } @ Override offentlig SimpleSource destinationToSource (SimpleDestination destination) {if (destination == null) {return null; } SimpleSource simpleSource = ny SimpleSource (); simpleSource.setName (destination.getName ()); simpleSource.setDescription (destination.getDescription ()); returner simpleSource; }}

4.4. En test sag

Endelig, med alt genereret, lad os skrive en test sag vil vise, at værdier i SimpleSource match værdier i SimpleDestination.

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

5. Kortlægning med afhængighedsinjektion

Lad os derefter hente en forekomst af en kortlægger i MapStruct ved blot at ringe Mappers.getMapper (YourClass.class).

Det er selvfølgelig en meget manuel måde at få forekomsten på - et meget bedre alternativ ville være at injicere kortlæggeren direkte, hvor vi har brug for det (hvis vores projekt bruger en løsning til afhængighedsinjektion).

Heldigvis har MapStruct solid støtte til både Spring og CDI (Kontekster og afhængighedsinjektion).

For at bruge Spring IoC i vores kortlægger skal vi tilføje componentModelattribut til @Mapper med værdien forår og for CDI ville være cdi .

5.1. Rediger kortlæggeren

Føj følgende kode til SimpleSourceDestinationMapper:

@Mapper (componentModel = "spring") offentlig grænseflade SimpleSourceDestinationMapper

6. Kortlægning af felter med forskellige feltnavne

Fra vores tidligere eksempel kunne MapStruct kortlægge vores bønner automatisk, fordi de har de samme feltnavne. Så hvad hvis en bønne, vi skal kortlægge, har et andet feltnavn?

For vores eksempel opretter vi en ny bøn kaldet Medarbejder og MedarbejderDTO.

6.1. Nye POJO'er

offentlig klasse EmployeeDTO {private int medarbejder-id; private String medarbejdernavn; // getters og setters}
offentlig klassemedarbejder {privat int id; privat strengnavn; // getters og setters}

6.2. Mapper-grænsefladen

Når vi kortlægger forskellige feltnavne, bliver vi nødt til at konfigurere kildefeltet til dets målfelt, og for at gøre det skal vi tilføje @Mappings kommentar. Denne kommentar accepterer en matrix af @Kortlægning annotation, som vi vil bruge til at tilføje mål- og kildeattributten.

I MapStruct kan vi også bruge punktnotation til at definere et medlem af en bønne:

@Mapper offentlig grænseflade EmployeeMapper {@Mappings ({@Mapping (target = "employeeId", source = "entity.id"), @Mapping (target = "employeeName", source = "entity.name")}) EmployeeDTO-medarbejderToEmployeeDTO ( Ansat enhed); @Mappings ({@Mapping (target = "id", source = "dto.employeeId"), @Mapping (target = "name", source = "dto.employeeName")}) MedarbejdermedarbejderDTOtoEmployee (EmployeeDTO dto); }

6.3. Testsagen

Igen skal vi teste, at både kilde- og destinationsobjektværdier matcher:

@Test offentligt ugyldigt givenEmployeeDTOwithDiffNametoEmployee_whenMaps_thenCorrect () {EmployeeDTO dto = new EmployeeDTO (); dto.setEmployeeId (1); dto.setEmployeeName ("John"); Medarbejder enhed = kortlægger.medarbejderDTOtoMedarbejder (dto); assertEquals (dto.getEmployeeId (), entity.getId ()); assertEquals (dto.getEmployeeName (), entity.getName ()); }

Flere testsager kan findes i Github-projektet.

7. Kortlægning af bønner med børnebønner

Dernæst viser vi, hvordan man kortlægger en bønne med henvisninger til andre bønner.

7.1. Rediger POJO

Lad os tilføje en ny bønnehenvisning til Medarbejder objekt:

offentlig klasse EmployeeDTO {private int medarbejder-id; private String medarbejdernavn; privat DivisionDTO division; // getters og setters udeladt}
offentlig klassemedarbejder {privat int id; privat strengnavn; privat division division; // getters og setters udeladt}
offentlig klasse Division {privat int id; privat strengnavn; // standardkonstruktør, getters og setters udeladt}

7.2. Rediger kortlæggeren

Her skal vi tilføje en metode til at konvertere Division til DivisionDTO og omvendt; Hvis MapStruct registrerer, at objekttypen skal konverteres, og metoden til konvertering findes i samme klasse, så bruger den den automatisk.

Lad os tilføje dette til kortlæggeren:

DivisionDTO divisionToDivisionDTO (Division enhed); Division divisionDTOtoDivision (DivisionDTO dto);

7.3. Rediger testsagen

Lad os ændre og tilføje et par testsager til den eksisterende:

@Test offentlig ugyldighed givenEmpDTONestedMappingToEmp_whenMaps_thenCorrect () {EmployeeDTO dto = ny EmployeeDTO (); dto.setDivision (ny DivisionDTO (1, "Division1")); Medarbejder enhed = kortlægger.medarbejderDTOtoMedarbejder (dto); assertEquals (dto.getDivision (). getId (), entity.getDivision (). getId ()); assertEquals (dto.getDivision (). getName (), entity.getDivision (). getName ()); }

8. Kortlægning med typekonvertering

MapStruct tilbyder også et par færdige konvertering af implicitte typer, og for vores eksempel vil vi forsøge at konvertere en strengdato til en faktisk Dato objekt.

For flere detaljer om implicit typekonvertering kan du læse MapStruct-referencevejledningen.

8.1. Modificer bønnerne

Tilføj en startdato for vores medarbejder:

offentlig klasse Medarbejder {// andre felter privat Dato startDt; // getters og setters}
offentlig klasse EmployeeDTO {// andre felter privat StrengmedarbejderStartDt; // getters og setters}

8.2. Rediger kortlæggeren

Rediger kortlæggeren, og angiv datoformat til vores startdato:

@Mappings ({@Mapping (target = "employeeId", source = "entity.id"), @Mapping (target = "medarbejdernavn", kilde = "enhed.navn"), @Mapping (target = "medarbejderStartDt", kilde) = "entity.startDt", dateFormat = "dd-MM-åååå HH: mm: ss")}) MedarbejderDTO-medarbejderToEmployeeDTO (Medarbejderenhed); @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")}) MedarbejdermedarbejderDTOtoMedarbejder (MedarbejderDTO dto);

8.3. Rediger testsagen

Lad os tilføje et par flere testtilfælde for at kontrollere, at konverteringen er korrekt:

privat statisk endelig streng DATE_FORMAT = "dd-MM-åååå HH: mm: ss"; @Test offentlig ugyldighed givenEmpStartDtMappingToEmpDTO_whenMaps_thenCorrect () kaster ParseException {Medarbejder enhed = ny medarbejder (); entity.setStartDt (ny dato ()); EmployeeDTO dto = mapper.employeeToEmployeeDTO (enhed); SimpleDateFormat format = nyt SimpleDateFormat (DATE_FORMAT); assertEquals (format.parse (dto.getEmployeeStartDt ()). toString (), entity.getStartDt (). toString ()); } @Test offentlig ugyldighed givenEmpDTOStartDtMappingToEmp_whenMaps_thenCorrect () kaster ParseException {EmployeeDTO dto = ny EmployeeDTO (); dto.setEmployeeStartDt ("01-04-2016 01:00:00"); Medarbejder enhed = kortlægger.medarbejderDTOtoMedarbejder (dto); SimpleDateFormat format = nyt SimpleDateFormat (DATE_FORMAT); assertEquals (format.parse (dto.getEmployeeStartDt ()). toString (), entity.getStartDt (). toString ()); }

9. Kortlægning med en abstrakt klasse

Nogle gange vil vi måske tilpasse vores kortlægger på en måde, der overstiger @Mapping-kapaciteter.

For eksempel kan vi ud over typekonvertering måske transformere værdierne på en eller anden måde som i vores eksempel nedenfor.

I et sådant tilfælde kan vi oprette en abstrakt klasse og implementere metoder, vi ønsker at have tilpasset og lade abstrakte dem, der skal genereres af MapStruct.

9.1. Grundlæggende model

I dette eksempel bruger vi følgende klasse:

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

og en matchende DTO:

offentlig klasse TransactionDTO {private String uuid; private Lange samlede INCENT; // standard getters og setter}

Den vanskelige del her er at konvertere BigDecimalTotalmængde dollars til en Lang totalInCents.

9.2. Definition af en kortlægger

Vi kan opnå dette ved at skabe vores Mapper som en abstrakt klasse:

@Mapper abstrakt klasse TransactionMapper {public TransactionDTO toTransactionDTO (Transaction transaction) {TransactionDTO transactionDTO = new TransactionDTO (); transactionDTO.setUuid (transaction.getUuid ()); transactionDTO.setTotalInCents (transaction.getTotal () .multiply (ny BigDecimal ("100")). longValue ()); returtransaktionDTO; } offentlig abstrakt Liste til TransaktionDTO (Indsamlingstransaktioner); }

Her har vi implementeret vores fuldt tilpassede kortlægningsmetode til en enkelt objektkonvertering.

På den anden side forlod vi metoden, der er beregnet til at kortlægge Kollektiontil en Listeabstrakt, så MapStruct vil implementere det for os.

9.3. Genereret resultat

Som vi allerede har implementeret metoden til kortlægning af enkelt Transaktionind i en TransaktionDTO, vi forventer Mapstructat bruge det i den anden metode. Følgende genereres:

@Generated class TransactionMapperImpl udvider TransactionMapper {@Override public List toTransactionDTO (Collection transaktioner) {if (transaktioner == null) {return null; } Liste liste = ny ArrayList (); for (Transaktionstransaktion: transaktioner) {list.add (toTransactionDTO (transaktion)); } returliste }}

Som vi kan se i linje 12, MapStruct bruger vores implementering i den metode, som den genererede.

10. Kommentarer før kortlægning og efter kortlægning

Her er en anden måde at tilpasse på @Kortlægning funktioner ved hjælp af @BeforeMapping og @AfterMapping kommentarer. Kommentarerne bruges til at markere metoder, der påberåbes lige før og efter kortlægningslogikken.

De er ret nyttige i scenarier, hvor vi måske ønsker det adfærd, der skal anvendes på alle kortlagte supertyper.

Lad os se på et eksempel, der kortlægger undertyperne af Bil; Elektrisk bil, og BioDieselCar, til CarDTO.

Under kortlægning vil vi kortlægge begrebet typer til FuelType enum-felt i DTO, og når kortlægningen er færdig, vil vi gerne ændre navnet på DTO til store bogstaver.

10.1. Grundlæggende model

I dette eksempel bruger vi følgende klasser:

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

Undertyper af Bil:

offentlig klasse BioDieselCar udvider bil {}
offentlig klasse ElectricCar udvider bil {}

Det CarDTO med en feltfeltype FuelType:

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

10.2. Definition af kortlæggeren

Lad os nu fortsætte og skrive vores abstrakte kortlægningsklasse, der kortlægges Bil til CarDTO:

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

@MappingTarget er en parameterkommentar, der udfylder måltilknytning DTO lige før kortlægningslogikken udføresi tilfælde af @BeforeMapping og lige efter i tilfælde af @AfterMapping kommenteret metode.

10.3. Resultat

Det CarsMapper defineret ovenfor generererdetimplementering:

@Generated public class CarsMapperImpl udvider CarsMapper {@Override public CarDTO toCarDto (Car car) {if (car == null) {return null; } CarDTO carDTO = ny CarDTO (); berig DTOWithFuelType (bil, carDTO); carDTO.setId (car.getId ()); carDTO.setName (car.getName ()); convertNameToUpperCase (carDTO); returnere carDTO; }}

Læg mærke til hvordan de annoterede metodeansøgninger omgiver kortlægningslogikken i implementeringen.

11. Støtte til Lombok

I den nyere version af MapStruct blev Lombok support annonceret. Så vi kan nemt kortlægge en kildeenhed og en destination ved hjælp af Lombok.

For at aktivere Lombok-support er vi nødt til at tilføje afhængigheden i kommentarprocessorstien. Så nu har vi mapstruct-processoren såvel som Lombok i Maven-compiler-pluginet:

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

Lad os definere kildeenheden ved hjælp af Lombok-annoteringer:

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

Og destinationsdataoverførselsobjektet:

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

Kortkortgrænsefladen til dette forbliver svarende til vores tidligere eksempel:

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

12. Støtte til standardekspression

Startende med version 1.3.0, vi kan bruge standardekspression attribut for @Kortlægning kommentar for at angive et udtryk, der bestemmer værdien for destinationsfeltet, hvis kildefeltet er nul. Dette er i tillæg til det eksisterende standard værdi attribut funktionalitet.

Kilden enhed:

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

Destinationsdataoverførselsobjektet:

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

Hvis den id felt for kildeenheden er nul, vi vil generere en tilfældig id og tildel det til destinationen og hold andre egenskabsværdier som de er:

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

Lad os tilføje en test sag for at kontrollere ekspressionens udførelse:

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

13. Konklusion

Denne artikel gav en introduktion til MapStruct. Vi har introduceret det meste af det grundlæggende i Mapping-biblioteket, og hvordan man bruger det i vores applikationer.

Implementeringen af ​​disse eksempler og tests findes i Github-projektet. Dette er et Maven-projekt, så det skal være let at importere og køre som det er.


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