Stručný sprievodca programom MapStruct

1. Prehľad

V tomto článku sa budeme venovať použitiu MapStruct, čo je zjednodušene povedané mapovač Java Bean.

Toto API obsahuje funkcie, ktoré sa automaticky mapujú medzi dvoma Java Beans. S MapStructom stačí vytvoriť rozhranie a knižnica počas kompilácie automaticky vytvorí konkrétnu implementáciu.

2. MapStruct a prenos objektov

U väčšiny aplikácií si všimnete mnohých štandardných kódov, ktoré prevádzajú POJO na iné POJO.

Bežný typ konverzie sa napríklad deje medzi entitami podporovanými perzistenciou a DTO, ktoré prechádzajú na stranu klienta.

To je problém, ktorý MapStruct riešimanuálne vytváranie mapovačov fazule je časovo náročné. Knižnica môže automaticky generovať triedy mapovačov fazule.

3. Maven

Pridajme nižšie závislosť do nášho Mavenu pom.xml:

 org.mapstruct mapstruct 1.3.1.Final 

Najnovšie stabilné vydanie aplikácie Mapstruct a jeho procesora sú dostupné v centrálnom úložisku Maven.

Pridajme tiež annotationProcessorPaths oddiel do konfiguračnej časti maven-compiler-plugin zapojiť.

The procesor mapy sa používa na generovanie implementácie mapovača počas zostavovania:

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

4. Základné mapovanie

4.1. Vytvorenie POJO

Najprv si vytvoríme jednoduchý Java POJO:

verejná trieda SimpleSource {súkromné ​​meno reťazca; popis súkromného reťazca; // getters and setters} public class SimpleDestination {private String name; popis súkromného reťazca; // zakladatelia a zakladatelia}

4.2. Rozhranie mapovača

@Mapper verejné rozhranie SimpleSourceDestinationMapper {SimpleDestination sourceToDestination (zdroj SimpleSource); SimpleSource destinationToSource (cieľ SimpleDestination); }

Všimnite si, že sme pre nás nevytvorili implementačnú triedu SimpleSourceDestinationMapper - pretože MapStruct ho vytvára pre nás.

4.3. Nový mapovač

Spracovanie MapStruct môžeme spustiť vykonaním mvn čistá inštalácia.

Toto vygeneruje implementačnú triedu pod / target / generované-zdroje / anotácie /.

Tu je trieda, ktorú pre nás MapStruct automaticky vytvára:

public class SimpleSourceDestinationMapperImpl implementuje SimpleSourceDestinationMapper {@Override public SimpleDestination sourceToDestination (zdroj SimpleSource) {if (source == null) {return null; } SimpleDestination simpleDestination = nový SimpleDestination (); simpleDestination.setName (source.getName ()); simpleDestination.setDescription (source.getDescription ()); návrat simpleDestination; } @Override public SimpleSource destinationToSource (SimpleDestination destination) {if (destination == null) {return null; } SimpleSource simpleSource = nový SimpleSource (); simpleSource.setName (destination.getName ()); simpleSource.setDescription (destination.getDescription ()); návrat simpleSource; }}

4.4. Testovací prípad

Nakoniec, so všetkým vygenerovaným, napíšeme testovací prípad, ktorý ukáže, že hodnoty sú v SimpleSource zhodné hodnoty v SimpleDestination.

verejná trieda SimpleSourceDestinationMapperIntegrationTest {private SimpleSourceDestinationMapper mapovač = Mappers.getMapper (SimpleSourceDestinationMapper.class); @ Test public void givenSourceToDestination_whenMaps_thenCorrect () {SimpleSource simpleSource = new SimpleSource (); simpleSource.setName ("SourceName"); simpleSource.setDescription ("SourceDescription"); Cieľ SimpleDestination = mapper.sourceToDestination (simpleSource); assertEquals (simpleSource.getName (), destination.getName ()); assertEquals (simpleSource.getDescription (), destination.getDescription ()); } @Test public void givenDestinationToSource_whenMaps_thenCorrect () {SimpleDestination destination = new SimpleDestination (); destination.setName ("DestinationName"); destination.setDescription ("DestinationDescription"); SimpleSource source = mapper.destinationToSource (cieľ); assertEquals (destination.getName (), source.getName ()); assertEquals (destination.getDescription (), source.getDescription ()); }}

5. Mapovanie pomocou injektáže závislostí

Ďalej získajme inštanciu mapovača v MapStructe iba zavolaním Mappers.getMapper (YourClass.class).

Je to samozrejme veľmi manuálny spôsob získania inštancie - oveľa lepšou alternatívou by bolo vstreknutie mapovača priamo tam, kde to potrebujeme (ak náš projekt používa akékoľvek riešenie Dependency Injection).

Našťastie MapStruct má pevnú podporu pre Spring aj CDI (Kontexty a vstrekovanie závislostí).

Ak chcete v našom mapovači použiť jarné IoC, musíme pridať komponentnýModelatribút @Mapper s hodnotou jar a pre CDI by bolo cdi .

5.1. Upravte mapovač

Pridajte nasledujúci kód do SimpleSourceDestinationMapper:

@Mapper (componentModel = "jar") verejné rozhranie SimpleSourceDestinationMapper

6. Mapovanie polí s rôznymi názvami polí

Z nášho predchádzajúceho príkladu bol MapStruct schopný mapovať naše fazule automaticky, pretože majú rovnaké názvy polí. Čo ak má fazuľa, ktorú sa chystáme mapovať, iný názov poľa?

Pre náš príklad vytvoríme novú fazuľu s názvom Zamestnanec a EmployeeDTO.

6.1. Nové POJO

public class EmployeeDTO {private int employeeId; private String employeeName; // zakladatelia a zakladatelia}
public class Employee {private int id; súkromné ​​meno reťazca; // zakladatelia a zakladatelia}

6.2. Rozhranie mapovača

Pri mapovaní rôznych názvov polí budeme musieť nakonfigurovať jeho zdrojové pole na cieľové pole a na to budeme musieť pridať @Mappings anotácia. Táto anotácia akceptuje pole @Mapovanie anotáciu, ktorú použijeme na pridanie atribútu cieľ a zdroj.

V MapStruct môžeme tiež použiť bodkovú notáciu na definovanie člena fazule:

@Mapper verejné rozhranie EmployeeMapper {@Mappings ({@Mapping (target = "employeeId", source = "entity.id"), @Mapping (target = "employeeName", source = "entity.name")}) EmployeeDTO employeeToEmployeeDTO ( Zamestnanec); @Mappings ({@Mapping (target = "id", source = "dto.employeeId"), @Mapping (target = "name", source = "dto.employeeName")}) Employee employeeDTOtoEmployee (EmployeeDTO dto); }

6.3. Testovací prípad

Opäť musíme vyskúšať, či sa hodnoty zdrojového aj cieľového objektu zhodujú:

@Test public void givenEmployeeDTOwithDiffNametoEmployee_whenMaps_thenCorrect () {EmployeeDTO dto = new EmployeeDTO (); dto.setEmployeeId (1); dto.setEmployeeName ("John"); Entita zamestnanca = mapper.employeeDTOtoEmployee (dto); assertEquals (dto.getEmployeeId (), entity.getId ()); assertEquals (dto.getEmployeeName (), entity.getName ()); }

Viac testovacích prípadov nájdete v projekte Github.

7. Mapovanie fazule s detskými fazuľami

Ďalej si ukážeme, ako mapovať fazuľu s odkazmi na ďalšie fazule.

7.1. Upravte POJO

Pridajme nový odkaz na fazuľa Zamestnanec objekt:

public class EmployeeDTO {private int employeeId; private String employeeName; súkromná divíziaDTO divízia; // vynechávači a nastavovatelia}
public class Employee {private int id; súkromné ​​meno reťazca; divízia súkromnej divízie; // vynechávači a nastavovatelia}
public class Division {private int id; súkromné ​​meno reťazca; // predvolený konštruktor, getre a setre vynechané}

7.2. Upravte mapovač

Tu musíme pridať metódu na prevod súboru Divízia do DivisionDTO a naopak; ak MapStruct zistí, že je potrebné previesť typ objektu a metóda prevodu existuje v rovnakej triede, použije ju automaticky.

Poďme to pridať do mapovača:

DivisionDTO divisionToDivisionDTO (divízny subjekt); Division divisionDTOtoDivision (DivisionDTO dto);

7.3. Upravte testovací prípad

Poďme upraviť a pridať niekoľko testovacích prípadov k existujúcemu:

@Test public void givenEmpDTONestedMappingToEmp_whenMaps_thenCorrect () {EmployeeDTO dto = new EmployeeDTO (); dto.setDivision (nová DivisionDTO (1, "Division1")); Entita zamestnanca = mapper.employeeDTOtoEmployee (dto); assertEquals (dto.getDivision (). getId (), entity.getDivision (). getId ()); assertEquals (dto.getDivision (). getName (), entity.getDivision (). getName ()); }

8. Mapovanie s konverziou typu

MapStruct ponúka aj niekoľko hotových konverzií implicitného typu a v našom príklade sa pokúsime previesť dátum reťazca na skutočný Dátum objekt.

Viac podrobností o implicitnej konverzii typov nájdete v referenčnej príručke k MapStruct.

8.1. Upravte fazuľa

Pridajte dátum začiatku nášho zamestnanca:

public class Zamestnanec {// ďalšie polia private Date startDt; // zakladatelia a zakladatelia}
verejná trieda EmployeeDTO {// ďalšie polia private String employeeStartDt; // zakladatelia a zakladatelia}

8.2. Upravte mapovač

Upravte mapovač a poskytnite formát dátumu pre náš dátum začatia:

@Mappings ({@Mapping (target = "employeeId", source = "entity.id"), @Mapping (target = "employeeName", source = "entity.name"), @Mapping (target = "employeeStartDt", zdroj = "entity.startDt", dateFormat = "dd-MM-rrrr HH: mm: ss")}) EmployeeDTO employeeToEmployeeDTO (entita zamestnanca); @Mappings ({@Mapping (target = "id", source = "dto.employeeId"), @Mapping (target = "name", source = "dto.employeeName"), @Mapping (target = "startDt", zdroj = "dto.employeeStartDt", dateFormat = "dd-MM-rrrr HH: mm: ss")}) Employee employeeDTOtoEmployee (EmployeeDTO dto);

8.3. Upravte testovací prípad

Pridajme niekoľko ďalších testovacích prípadov na overenie správnosti konverzie:

private static final String DATE_FORMAT = "dd-MM-rrrr HH: mm: ss"; @ Test public void givenEmpStartDtMappingToEmpDTO_whenMaps_thenCorrect () hodí ParseException {entita zamestnanca = nový zamestnanec (); entity.setStartDt (nový dátum ()); EmployeeDTO dto = mapper.employeeToEmployeeDTO (entita); Formát SimpleDateFormat = nový SimpleDateFormat (DATE_FORMAT); assertEquals (format.parse (dto.getEmployeeStartDt ()). toString (), entity.getStartDt (). toString ()); } @Test public void givenEmpDTOStartDtMappingToEmp_whenMaps_thenCorrect () hodí ParseException {EmployeeDTO dto = new EmployeeDTO (); dto.setEmployeeStartDt ("01-04-2016 01:00:00"); Entita zamestnanca = mapper.employeeDTOtoEmployee (dto); Formát SimpleDateFormat = nový SimpleDateFormat (DATE_FORMAT); assertEquals (format.parse (dto.getEmployeeStartDt ()). toString (), entity.getStartDt (). toString ()); }

9. Mapovanie pomocou abstraktnej triedy

Niekedy môžeme chcieť prispôsobiť náš mapovač spôsobom, ktorý presahuje možnosti @Mapping.

Napríklad okrem prevodu typu môžeme chcieť transformovať hodnoty nejakým spôsobom ako v našom príklade nižšie.

V takom prípade môžeme vytvoriť abstraktnú triedu a implementovať metódy, ktoré chceme prispôsobiť, a ponechať abstraktné tie, ktoré by mali byť generované MapStructom.

9.1. Základný model

V tomto príklade použijeme nasledujúcu triedu:

verejná trieda Transakcia {private Long id; private String uuid = UUID.randomUUID (). toString (); súkromný BigDecimal súčet; // štandardní príjemcovia}

a zodpovedajúci DTO:

verejná trieda TransactionDTO {private String uuid; private Long totalInCents; // štandardné getre a setre}

Zložitou časťou je prevádzanie BigDecimalCelkomsumu dolárov do a Dlhý celkový cent.

9.2. Definovanie mapovača

To môžeme dosiahnuť vytvorením našej Mapovač ako abstraktná trieda:

@Mapper abstraktná trieda TransactionMapper {verejná TransactionDTO toTransactionDTO (transakčná transakcia) {TransactionDTO transactionDTO = nová TransactionDTO (); transactionDTO.setUuid (transaction.getUuid ()); transactionDTO.setTotalInCents (transaction.getTotal () .multiply (nový BigDecimal ("100")). longValue ()); návratová transakcia DTO; } verejný abstrakt Zoznam toTransactionDTO (inkasné transakcie); }

Tu sme implementovali našu plne prispôsobenú metódu mapovania pre prevod jedného objektu.

Na druhej strane sme opustili metódu, ktorá je určená na mapovanie Zbierkado a Zoznamabstraktné, tak MapStruct to za nás implementuje.

9.3. Generovaný výsledok

Ako sme už implementovali metódu mapovania singlov Transakciado a TransactionDTO, očakávame Mapstructpoužiť v druhej metóde. Bude vygenerované toto:

@ Generovaná trieda TransactionMapperImpl rozširuje TransactionMapper {@Override verejný zoznam na TransactionDTO (transakcie zbierky) {if (transakcie == null) {return null; } Zoznam zoznam = new ArrayList (); pre (Transakčná transakcia: transakcie) {list.add (toTransactionDTO (transakcia)); } návratový zoznam; }}

Ako vidíme na riadku 12, MapStruct používa našu implementáciu v metóde, ktorú vygenerovala.

10. Pred-mapovacie a dodatočné mapovacie anotácie

Toto je ďalší spôsob prispôsobenia @Mapovanie schopnosti pomocou @BeforeMapping a @AfterMapping anotácie. Anotácie sa používajú na označenie metód, ktoré sa vyvolajú priamo pred a po mapovacej logike.

Sú celkom užitočné v scenároch, kde by sme to mohli chcieť správanie, ktoré sa má aplikovať na všetky mapované supertypy.

Pozrime sa na príklad, ktorý mapuje podtypy Auto; Elektrické auto, a BioDieselCar, do CarDTO.

Pri mapovaní by sme chceli zmapovať pojem typov na Typ paliva enum v DTO a po vykonaní mapovania by sme chceli zmeniť názov DTO na veľké písmená.

10.1. Základný model

V tomto príklade použijeme nasledujúce triedy:

public class Car {private int id; súkromné ​​meno reťazca; }

Podtypy Auto:

verejná trieda BioDieselCar rozširuje auto {}
verejná trieda ElectricCar rozširuje auto {}

The CarDTO s typom poľa enum Typ paliva:

public class CarDTO {private int id; súkromné ​​meno reťazca; súkromný FuelType druh paliva; }
verejné enum FuelType {ELECTRIC, BIO_DIESEL}

10.2. Definovanie mapovača

Teraz pokračujme a napíšme našu abstraktnú triedu mapovačov, tieto mapy Auto do CarDTO:

@Mapper public abstract class CarsMapper {@BeforeMapping protected void enrichDTOWithFuelType (Car car, @MappingTarget CarDTO carDto) {if (car instanceof ElectricCar) {carDto.setFuelType (FuelType.ELECTRIC); } if (auto instanceof BioDieselCar) {carDto.setFuelType (FuelType.BIO_DIESEL); }} @AfterMapping chránený neplatný convertNameToUpperCase (@MappingTarget CarDTO carDto) {carDto.setName (carDto.getName (). ToUpperCase ()); } verejný abstrakt CarDTO toCarDto (auto do auta); }

@MappingTarget je anotácia parametra, ktorá naplní cieľové mapovacie DTO tesne pred vykonaním mapovacej logikyv prípade @BeforeMapping a hneď potom v prípade @AfterMapping komentovaná metóda.

10.3. Výsledok

The CarsMapper definované vyššie generujetheimplementácia:

@ Generovaná verejná trieda CarsMapperImpl rozširuje CarsMapper {@Override public CarDTO toCarDto (Car car) {if (car == null) {return null; } CarDTO carDTO = nový CarDTO (); enrichDTOWithFuelType (auto, carDTO); carDTO.setId (car.getId ()); carDTO.setName (car.getName ()); convertNameToUpperCase (carDTO); spiatočné auto DTO; }}

Všimnite si ako anotované vyvolania metód obklopujú logiku mapovania pri implementácii.

11. Podpora pre Lombok

V nedávnej verzii MapStruct bola oznámená podpora Lomboku. Takže pomocou Lomboku môžeme ľahko zmapovať zdrojovú entitu a cieľ.

Aby sme povolili podporu Lomboku, musíme pridať závislosť do cesty anotačného procesora. Takže teraz máme procesor mapstruct rovnako ako Lombok v zásuvnom programe kompilátora Maven:

 org.apache.maven.plugins maven-compiler-plugin 3.5.1 1.8 1.8 org.mapstruct mapstruct-processor 1.3.1. Konečný org.projectlombok lombok 1.18.4 

Definujme zdrojovú entitu pomocou Lombokových anotácií:

@Getter @Setter verejná trieda Auto {private int id; súkromné ​​meno reťazca; }

A cieľový objekt prenosu údajov:

@Getter @Setter verejná trieda CarDTO {private int id; súkromné ​​meno reťazca; }

Mapovacie rozhranie pre toto zostáva podobné nášmu predchádzajúcemu príkladu:

@Mapper verejné rozhranie CarMapper {CarMapper INSTANCE = Mappers.getMapper (CarMapper.class); CarDTO carToCarDTO (Car car); }

12. Podpora pre defaultExpression

Počnúc verziou 1.3.0, môžeme použiť defaultExpression atribút @Mapovanie anotácia na určenie výrazu, ktorý určuje hodnotu cieľového poľa, ak je zdrojové pole nulový. Toto je dodatok k existujúcemu predvolená hodnota funkčnosť atribútu.

Zdrojová entita:

public class Osoba {private int id; súkromné ​​meno reťazca; }

Cieľový objekt prenosu údajov:

verejná trieda PersonDTO {private int id; súkromné ​​meno reťazca; }

Ak id pole zdrojovej entity je nulový, chceme vygenerovať náhodný id a priradiť ho k cieľu, pričom ponechá ďalšie hodnoty vlastností tak, ako sú:

@Mapper verejné rozhranie PersonMapper {PersonMapper INSTANCE = Mappers.getMapper (PersonMapper.class); @Mapping (target = "id", source = "person.id", defaultExpression = "java (java.util.UUID.randomUUID (). ToString ())") PersonDTO personToPersonDTO (Osoba osoba); }

Pridajme testovací prípad na overenie vykonania výrazu:

@Test public void givenPersonEntitytoPersonWithExpression_whenMaps_thenCorrect () Osoba entity = nová Osoba (); entity.setName ("Micheal"); PersonDTO personDto = PersonMapper.INSTANCE.personToPersonDTO (entita); assertNull (entity.getId ()); assertNotNull (personDto.getId ()); assertEquals (personDto.getName (), entity.getName ()); }

13. Záver

Tento článok poskytol úvod do aplikácie MapStruct. Predstavili sme väčšinu základných informácií o knižnici Mapping a o tom, ako ju používať v našich aplikáciách.

Implementáciu týchto príkladov a testov možno nájsť v projekte Github. Toto je projekt Maven, takže by malo byť ľahké ho importovať a spustiť tak, ako je.


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