Kratki vodič za MapStruct

1. Pregled

U ovom ćemo članku istražiti upotrebu MapStruct-a koji je, jednostavno rečeno, mapper Java Bean-a.

Ovaj API sadrži funkcije koje automatski mapiraju dva Java Bean-a. S MapStructom trebamo stvoriti samo sučelje, a knjižnica će automatski stvoriti konkretnu implementaciju tijekom vremena kompajliranja.

2. MapStruct i prijenos uzorka objekta

Za većinu aplikacija primijetit ćete puno standardnih kodova koji pretvaraju POJO-ove u druge POJO-ove.

Na primjer, uobičajena vrsta pretvorbe događa se između entiteta podržanih postojanošću i DTO-a koji izlaze na klijentsku stranu.

Dakle, to je problem koji MapStruct rješavaručno stvaranje mapa graha dugo traje. Knjižnica može automatski generirati klase mapiranja graha.

3. Maven

Dodajmo donju ovisnost u naš Maven pom.xml:

 org.mapstruct mapstruct 1.3.1.Final 

Najnovije stabilno izdanje Mapstruct-a i njegov procesor dostupni su iz Maven Central Repozitorija.

Dodajmo i annotationProcessorPaths odjeljak na dio za konfiguraciju maven-compiler-plugin uključiti.

The mapstruct-procesor koristi se za generiranje implementacije mappera tijekom gradnje:

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

4. Osnovno mapiranje

4.1. Izrada POJO-a

Stvorimo prvo jednostavan Java POJO:

javna klasa SimpleSource {naziv privatnog niza; opis privatnog niza; // getteri i postavljači} javna klasa SimpleDestination {naziv privatnog niza; opis privatnog niza; // geteri i postavljači}

4.2. Sučelje Mapper

@Mapper javno sučelje SimpleSourceDestinationMapper {SimpleDestination sourceToDestination (izvor SimpleSource); SimpleSource destinationToSource (odredište SimpleDestination); }

Primijetite da nismo stvorili klasu implementacije za našu SimpleSourceDestinationMapper - jer MapStruct to stvara za nas.

4.3. Novi Mapper

Obradu MapStruct možemo pokrenuti izvršavanjem datoteke mvn čista instalacija.

Ovo će generirati klasu implementacije pod / target / generirani izvori / napomene /.

Evo klase koju MapStruct automatski kreira za nas:

javna klasa SimpleSourceDestinationMapperImpl implementira SimpleSourceDestinationMapper {@Override public SimpleDestination sourceToDestination (SimpleSource source) {if (source == null) {return null; } SimpleDestination simpleDestination = novo SimpleDestination (); simpleDestination.setName (source.getName ()); simpleDestination.setDescription (source.getDescription ()); povratak simpleDestination; } @Override public SimpleSource destinationToSource (odredište SimpleDestination) {if (destination == null) {return null; } SimpleSource simpleSource = novi SimpleSource (); simpleSource.setName (destination.getName ()); simpleSource.setDescription (destination.getDescription ()); povratak simpleSource; }}

4.4. Ispitni slučaj

Konačno, sa svime generiranim, napišimo test slučaj koji će pokazati da vrijednosti u SimpleSource podudaraju vrijednosti u Jednostavno odredište.

javna klasa SimpleSourceDestinationMapperIntegrationTest {private SimpleSourceDestinationMapper mapper = Mappers.getMapper (SimpleSourceDestinationMapper.class); @Test javna praznina givenSourceToDestination_whenMaps_thenCorrect () {SimpleSource simpleSource = novi SimpleSource (); simpleSource.setName ("SourceName"); simpleSource.setDescription ("SourceDescription"); Odredište SimpleDestination = mapper.sourceToDestination (simpleSource); assertEquals (simpleSource.getName (), destination.getName ()); assertEquals (simpleSource.getDescription (), destination.getDescription ()); } @Test javna praznina givenDestinationToSource_whenMaps_thenCorrect () {Odredište SimpleDestination = novo SimpleDestination (); destination.setName ("Ime odredišta"); destination.setDescription ("DestinationDescription"); SimpleSource izvor = mapper.destinationToSource (odredište); assertEquals (odredište.getName (), source.getName ()); assertEquals (destination.getDescription (), source.getDescription ()); }}

5. Mapiranje injekcijom ovisnosti

Zatim, pribavimo instancu mappera u MapStructu jednostavnim pozivom Mappers.getMapper (YourClass.class).

Naravno, to je vrlo ručni način dobivanja instance - puno bolja alternativa bila bi ubrizgavanje mappera izravno tamo gdje nam treba (ako naš projekt koristi bilo koje rješenje ubrizgavanja ovisnosti).

Srećom MapStruct ima solidnu podršku i za Spring i za CDI (Konteksti i ubrizgavanje ovisnosti).

Da bismo koristili Spring IoC u našoj mapi, moramo dodati komponentaModelpripisati @Mapper s vrijednošću Proljeće a za CDI bi bio cdi .

5.1. Izmijenite Mapper

Dodajte sljedeći kod u SimpleSourceDestinationMapper:

@Mapper (componentModel = "spring") javno sučelje SimpleSourceDestinationMapper

6. Mapiranje polja s različitim imenima polja

Iz našeg prethodnog primjera, MapStruct je uspio automatski mapirati naš grah jer imaju ista imena polja. Pa što ako grah koji ćemo mapirati ima drugačiji naziv polja?

Za naš ćemo primjer stvoriti novi grah koji se zove Zaposlenik i EmployeeDTO.

6.1. Novi POJO

javna klasa EmployeeDTO {private int workerId; private String workerName; // geteri i postavljači}
zaposlenik u javnoj klasi {private int id; privatni naziv niza; // geteri i postavljači}

6.2. Sučelje Mapper

Kada mapiramo različita imena polja, morat ćemo konfigurirati njegovo izvorno polje u njegovo ciljno polje, a za to ćemo morati dodati @Mappings bilješka. Ova napomena prihvaća niz @ Mapiranje napomena koju ćemo upotrijebiti za dodavanje ciljnog i izvornog atributa.

U MapStructu također možemo koristiti točkaste zapise za definiranje člana graha:

Javno sučelje @Mapper EmployeeMapper {@Mappings ({@Mapping (target = "workerId", source = "entity.id"), @Mapping (target = "workerName", source = "entity.name")}) EmployeeDTO workerToEfficieeDTO ( Entitet zaposlenika); @Mappings ({@Mapping (target = "id", source = "dto.employeeId"), @Mapping (target = "name", source = "dto.employeeName")}) Employee workerDTOtoE Employee (EmployeeDTO dto); }

6.3. Ispitni slučaj

Opet moramo testirati podudaraju li se i izvorne i odredišne ​​vrijednosti objekta:

@Test javna praznina givenEfficieeDTOwithDiffNametoEfficiee_whenMaps_thenCorrect () {EmployeeDTO dto = new EmployeeDTO (); dto.setEmploymenteeId (1); dto.setEfficieeName ("Ivan"); Entitet zaposlenika = mapper.employeeDTOtoE Employee (dto); assertEquals (dto.getEfficieeId (), entity.getId ()); assertEquals (dto.getEfficieeName (), entity.getName ()); }

Više test slučajeva može se naći u projektu Github.

7. Mapiranje graha s dječjim grahom

Zatim ćemo pokazati kako mapirati grah s referencama na drugi grah.

7.1. Izmijenite POJO

Dodajmo novu referencu graha na Zaposlenik objekt:

javna klasa EmployeeDTO {private int workerId; private String workerName; privatni odjelDTO odjel; // izostavljeni geteri i postavljači}
zaposlenik u javnoj klasi {private int id; privatni naziv niza; privatna divizija; // izostavljeni geteri i postavljači}
odjeljenje javne klase {private int id; privatni naziv niza; // izostavljeni zadani konstruktor, getteri i postavljači}

7.2. Izmijenite Mapper

Ovdje trebamo dodati metodu za pretvorbu datoteke Podjela do DivisionDTO i obrnuto; ako MapStruct otkrije da vrstu objekta treba pretvoriti i metoda za pretvorbu postoji u istoj klasi, tada će je automatski koristiti.

Dodajmo ovo u mapper:

DivisionDTO divisionToDivisionDTO (entitet odjela); Divizija DivisionDTOtoDivision (DivisionDTO dto);

7.3. Izmijenite test slučaj

Izmijenimo i dodajte nekoliko postojećih slučajeva:

@Test javna praznina givenEmpDTONestedMappingToEmp_whenMaps_thenCorrect () {EmployeeDTO dto = new EmployeeDTO (); dto.setDivision (novi DivisionDTO (1, "Division1")); Entitet zaposlenika = mapper.employeeDTOtoE Employee (dto); assertEquals (dto.getDivision (). getId (), entity.getDivision (). getId ()); assertEquals (dto.getDivision (). getName (), entity.getDivision (). getName ()); }

8. Mapiranje s pretvorbom tipa

MapStruct također nudi nekoliko gotovih konverzija implicitnog tipa, a za naš ćemo primjer pokušati pretvoriti datum niza u stvarni Datum objekt.

Za više detalja o implicitnoj pretvorbi tipa, možete pročitati referentni vodič MapStruct.

8.1. Izmijenite grah

Dodajte datum početka za našeg zaposlenika:

javna klasa zaposlenik {// ostala polja privatno Datum startDt; // geteri i postavljači}
javna klasa EmployeeDTO {// ostala polja private String workerStartDt; // geteri i postavljači}

8.2. Izmijenite Mapper

Izmijenite mapper i dostavite Oblik datuma za naš datum početka:

@Mappings ({@Mapping (target = "workerId", source = "entity.id"), @Mapping (target = "workerName", source = "entity.name"), @Mapping (target = "workerStartDt", source = "entity.startDt", dateFormat = "dd-MM-yyyy HH: mm: ss")}) EmployeeDTO workerToEfficieeDTO (entitet zaposlenika); @Mappings ({@Mapping (target = "id", source = "dto.employeeId"), @Mapping (target = "name", source = "dto.employeeName"), @Mapping (target = "startDt", source = "dto.employeeStartDt", dateFormat = "dd-MM-yyyy HH: mm: ss")}) Zaposleni zaposlenikDTOtoE Employee (EmployeeDTO dto);

8.3. Izmijenite test slučaj

Dodajmo još nekoliko testnih slučajeva kako bismo provjerili je li pretvorba ispravna:

privatni statički završni niz DATE_FORMAT = "dd-MM-yyyy HH: mm: ss"; @Test public void givenEmpStartDtMappingToEmpDTO_whenMaps_thenCorrect () baca ParseException {Entitet zaposlenika = novi zaposlenik (); entity.setStartDt (novi datum ()); EmployeeDTO dto = mapper.employeeToEfficieeDTO (entitet); SimpleDateFormat format = novi SimpleDateFormat (DATE_FORMAT); assertEquals (format.parse (dto.getEfficieeStartDt ()). toString (), entity.getStartDt (). toString ()); } @Test public void givenEmpDTOStartDtMappingToEmp_whenMaps_thenCorrect () baca ParseException {EmployeeDTO dto = new EmployeeDTO (); dto.setEfficieeStartDt ("01-04-2016 01:00:00"); Entitet zaposlenika = mapper.employeeDTOtoE Employee (dto); SimpleDateFormat format = novi SimpleDateFormat (DATE_FORMAT); assertEquals (format.parse (dto.getEfficieeStartDt ()). toString (), entity.getStartDt (). toString ()); }

9. Mapiranje apstraktnim razredom

Ponekad bismo mogli prilagoditi svoj mapper na način koji premašuje mogućnosti @Mapping.

Na primjer, uz pretvorbu tipa, možda ćemo htjeti transformirati vrijednosti na neki način kao u našem primjeru u nastavku.

U tom slučaju možemo stvoriti apstraktnu klasu i implementirati metode koje želimo prilagoditi, a apstraktne ostaviti one koje bi MapStruct trebao generirati.

9.1. Osnovni model

U ovom ćemo primjeru koristiti sljedeću klasu:

javna klasa Transakcija {private Long id; privatni niz uuid = UUID.randomUUID (). toString (); privatni BigDecimal ukupno; // standardni geteri}

i odgovarajući DTO:

javna klasa TransactionDTO {private String uuid; privatni Long totalInCents; // standardni geteri i postavljači}

Ovdje je lukav dio pretvaranje BigDecimalukupnoiznos dolara u Dugo ukupnoInCents.

9.2. Definiranje Mappera

To možemo postići stvaranjem našeg Mapper kao apstraktni razred:

@Mapper apstraktna klasa TransactionMapper {public TransactionDTO toTransactionDTO (Transakcijska transakcija) {TransactionDTO transactionDTO = nova TransactionDTO (); actionDTO.setUuid (action.getUuid ()); actionDTO.setTotalInCents (action.getTotal () .multiply (novi BigDecimal ("100")). longValue ()); povrat transakcijeDTO; } javni sažetak Popis toTransactionDTO (Transakcije naplate); }

Ovdje smo implementirali našu potpuno prilagođenu metodu mapiranja za pretvorbu jednog objekta.

S druge strane, ostavili smo metodu koja je namijenjena mapiranju Kolekcijado a Popisapstraktno, dakle MapStruct će to primijeniti umjesto nas.

9.3. Generirani rezultat

Kao što smo već implementirali metodu za mapiranje pojedinačnih Transakcijau a TransactionDTO, očekujemo Mapstructda ga upotrijebim u drugoj metodi. Generirat će se sljedeće:

@ Generirana klasa TransactionMapperImpl proširuje TransactionMapper {@Override javni popis naTransactionDTO (Transakcije prikupljanja) {if (transakcije == null) {return null; } Popis popisa = novi ArrayList (); za (Transakcijska transakcija: transakcije) {list.add (toTransactionDTO (transakcija)); } popis za povratak; }}

Kao što vidimo u retku 12, MapStruct koristi našu implementaciju u metodi koju je generirala.

10. Bilješke prije mapiranja i naknadnog mapiranja

Evo još jednog načina prilagođavanja @ Mapiranje mogućnosti pomoću @BeforeMapping i @AfterMapping bilješke. Bilješke se koriste za označavanje metoda koje se pozivaju neposredno prije i nakon logike mapiranja.

Prilično su korisni u scenarijima u kojima bismo ovo mogli željeti ponašanje koje će se primijeniti na sve mapirane supertipove.

Pogledajmo primjer koji mapira podvrste Automobil; Električni auto, i BioDieselCar, do CarDTO.

Tijekom mapiranja željeli bismo mapirati pojam tipova u Tip goriva enum polje u DTO-u, a nakon što se mapiranje završi željeli bismo promijeniti naziv DTO-a u veliko slovo.

10.1. Osnovni model

U ovom ćemo primjeru koristiti sljedeće tečajeve:

javni razred automobila {private int id; privatni naziv niza; }

Podvrste Automobil:

javna klasa BioDieselCar proširuje automobil {}
javni razred ElectricCar proširuje automobil {}

The CarDTO s tipom polja enum Tip goriva:

javna klasa CarDTO {private int id; privatni naziv niza; privatni FuelType tip goriva; }
javni popis vrste goriva {ELECTRIC, BIO_DIESEL}

10.2. Definiranje Mappera

Sada nastavimo i napišite našu klasu apstraktnih mappera, to mape Automobil do CarDTO:

Javna apstraktna klasa @Mapper CarsMapper {@BeforeMapping zaštićena je praznina enrichDTOWithFuelType (Automobil, @MappingTarget CarDTO carDto) {if (automobil instance of ElectricCar) {carDto.setFuelType (FuelType.ELECTRIC); } if (primjer automobila BioDieselCar) {carDto.setFuelType (FuelType.BIO_DIESEL); }} @AfterMapping zaštićena praznina convertNameToUpperCase (@MappingTarget CarDTO carDto) {carDto.setName (carDto.getName (). ToUpperCase ()); } javni sažetak CarDTO toCarDto (automobil automobila); }

@MappingTarget je oznaka parametra koja popunjava ciljni DTO mapiranja neposredno prije izvođenja logike mapiranjau slučaju @BeforeMapping i odmah nakon u slučaju @AfterMapping anotirana metoda.

10.3. Proizlaziti

The CarsMapper definirano gore generiratheprovedba:

@ Generirana javna klasa CarsMapperImpl proširuje CarsMapper {@Premjesti javni CarDTO naCarDto (automobil automobila) {if (car == null) {return null; } CarDTO carDTO = novi CarDTO (); enrichDTOWithFuelType (auto, carDTO); carDTO.setId (car.getId ()); carDTO.setName (car.getName ()); convertNameToUpperCase (carDTO); povratak carDTO; }}

Primijetite kako zazivi anotiranih metoda okružuju logiku mapiranja u provedbi.

11. Podrška za Lombok

U nedavnoj verziji MapStructa najavljena je podrška za Lombok. Tako možemo lako mapirati izvorni entitet i odredište pomoću Lomboka.

Da bismo omogućili podršku za Lombok, moramo dodati ovisnost u stazu procesora bilješki. Dakle, sada imamo mapstruct-procesor kao i Lombok u dodatku za kompilaciju Maven:

 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 

Definirajmo izvorni entitet pomoću Lombok napomena:

@Getter @Setter javni razred automobila {private int id; privatni naziv niza; }

I odredišni objekt prijenosa podataka:

@Getter @Setter javna klasa CarDTO {private int id; privatni naziv niza; }

Sučelje mapiranja za ovo ostaje slično našem prethodnom primjeru:

@Mapper javno sučelje CarMapper {CarMapper INSTANCE = Mappers.getMapper (CarMapper.class); CarDTO carToCarDTO (Automobil); }

12. Podrška za defaultExpression

Počevši od verzije 1.3.0, možemo koristiti defaultExpression atribut @ Mapiranje napomena za specificiranje izraza koji određuje vrijednost odredišnog polja ako je izvorno polje null. Ovo je uz postojeće zadana vrijednost atributna funkcionalnost.

Izvorni entitet:

javna klasa Osoba {private int id; privatni naziv niza; }

Odredišni objekt prijenosa podataka:

javna klasa PersonDTO {private int id; privatni naziv niza; }

Ako je iskaznica polje izvornog entiteta je null, želimo generirati slučajni iskaznica i dodijelite ga odredištu zadržavajući ostale vrijednosti svojstva kakve jesu:

@Mapper javno sučelje PersonMapper {PersonMapper INSTANCE = Mappers.getMapper (PersonMapper.class); @Mapiranje (target = "id", source = "person.id", defaultExpression = "java (java.util.UUID.randomUUID (). ToString ())") PersonDTO personToPersonDTO (Osoba osoba); }

Dodajmo test za provjeru izvršavanja izraza:

@Test javna praznina givenPersonEntitytoPersonWithExpression_whenMaps_thenCorrect () Osoba entitet = nova osoba (); entity.setName ("Micheal"); PersonDTO personDto = PersonMapper.INSTANCE.personToPersonDTO (entitet); assertNull (entity.getId ()); assertNotNull (personDto.getId ()); assertEquals (personDto.getName (), entity.getName ()); }

13. Zaključak

Ovaj je članak pružio uvod u MapStruct. Uveli smo većinu osnova knjižnice Mapiranje i kako je koristiti u našim aplikacijama.

Provedba ovih primjera i testova može se naći u projektu Github. Ovo je Maven projekt, pa bi ga trebalo biti lako uvesti i pokrenuti kakav jest.