Rövid útmutató a MapStruct-hoz

1. Áttekintés

Ebben a cikkben megvizsgáljuk a MapStruct használatát, amely egyszerűen fogalmazva egy Java Bean leképező.

Ez az API olyan funkciókat tartalmaz, amelyek automatikusan leképeznek két Java babot. A MapStruct használatával csak az interfészt kell létrehoznunk, és a könyvtár a fordítási idő alatt automatikusan létrehoz egy konkrét megvalósítást.

2. MapStruct és az objektum átvitele

A legtöbb alkalmazásnál sok kazán kódot észlel, amely a POJO-kat más POJO-kká konvertálja.

Például egy általános típusú átalakítás történik a tartósságot támogató entitások és a kliens oldalon lévő DTO-k között.

Tehát ezt a problémát oldja meg a MapStructbableképezők manuális létrehozása időigényes. A könyvtár automatikusan generálhat bean mapper osztályokat.

3. Maven

Tegyük fel az alábbi függőséget Mavenünkbe pom.xml:

 org.mapstruct mapstruct 1.3.1.Végső 

A Mapstruct legújabb verziója és processzora a Maven központi adattárából érhető el.

Tegyük hozzá a annotationProcessorPaths szakasz a. konfigurációs részéhez maven-compiler-plugin csatlakoztat.

A mapstruct-processzor a leképező implementációjának létrehozására szolgál a build során:

 org.apache.maven.plugins maven-compiler-plugin 3.5.1 1.8 1.8 org.mapstruct mapstruct-processzor 1.3.1.Végső 

4. Alapvető feltérképezés

4.1. POJO létrehozása

Először hozzunk létre egy egyszerű Java POJO-t:

public class SimpleSource {privát karakterlánc neve; privát karakterlánc leírása; // getterek és beállítók} public class SimpleDestination {private String name; privát karakterlánc leírása; // szerelők és beállítók}

4.2. A Mapper felület

@Mapper nyilvános felület SimpleSourceDestinationMapper {SimpleDestination sourceToDestination (SimpleSource forrás); SimpleSource destinationToSource (SimpleDestination cél); }

Figyeljük meg, hogy nem hoztunk létre megvalósítási osztályt a sajátunk számára SimpleSourceDestinationMapper - mert a MapStruct hozza létre számunkra.

4.3. Az Új térképező

A MapStruct feldolgozását egy futtatásával tudjuk kiváltani mvn tiszta telepítés.

Ez létrehozza a végrehajtási osztályt a / target / generated-sources / annotations /.

Itt van az az osztály, amelyet a MapStruct automatikusan létrehoz nekünk:

public class SimpleSourceDestinationMapperImpl implementálja a SimpleSourceDestinationMapper {@Orride public SimpleDestination sourceToDestination (SimpleSource source) {if (source == null) {return null; } SimpleDestination simpleDestination = új SimpleDestination (); simpleDestination.setName (forrás.getName ()); simpleDestination.setDescription (forrás.getDescription ()); return simpleDestination; } @Orride public SimpleSource destinationToSource (SimpleDestination destination) {if (destination == null) {return null; } SimpleSource simpleSource = új SimpleSource (); simpleSource.setName (destination.getName ()); simpleSource.setDescription (destination.getDescription ()); return simpleSource; }}

4.4. Egy teszteset

Végül mindent generálva írjunk egy tesztesetet, amely megmutatja ezeket az értékeket SimpleSource értékek egyezése SimpleDestination.

public class SimpleSourceDestinationMapperIntegrationTest {private SimpleSourceDestinationMapper mapper = Mappers.getMapper (SimpleSourceDestinationMapper.class); @Test public void givenSourceToDestination_whenMaps_thenCorrect () {SimpleSource simpleSource = new SimpleSource (); simpleSource.setName ("ForrásNév"); simpleSource.setDescription ("SourceDescription"); SimpleDestination destination = 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 ("CélNév"); destination.setDescription ("DestinationDescription"); SimpleSource forrás = mapper.destinationToSource (cél); assertEquals (cél.címNév (), forrás.címNév ()); assertEquals (cél.getDescription (), forrás.getDescription ()); }}

5. Feltérképezés függőségi injekcióval

Ezután szerezzünk be egy térképkészítő példányt a MapStruct-ban pusztán hívással Mappers.getMapper (YourClass.class).

Természetesen ez egy nagyon manuális módszer a példány megszerzésére - sokkal jobb alternatíva lenne, ha a térképkészítőt közvetlenül oda injektálnánk, ahol szükségünk van rá (ha projektünk bármilyen függőségi injekciós megoldást használ).

A MapStruct szerencsére szilárdan támogatja mind a Springet, mind a CDI-t (Környezet és függőség-injektálás).

A Spring IoC használatához térképkészítőnkben hozzá kell adnunk a componentModeltulajdonít neki @Mapper az értékkel tavaszi és a CDI számára az lenne cdi .

5.1. Módosítsa a Mappert

Adja hozzá a következő kódot ide: SimpleSourceDestinationMapper:

@Mapper (componentModel = "spring") nyilvános felület SimpleSourceDestinationMapper

6. Mezők feltérképezése különböző mezőnevekkel

Korábbi példánk alapján a MapStruct képes volt automatikusan feltérképezni babjainkat, mert ugyanazok a mezõnevek. Tehát mi van akkor, ha egy babnak, amelyet feltérképezni készülünk, más a mező neve?

Példaként egy új babot fogunk létrehozni Munkavállaló és AlkalmazottDTO.

6.1. Új POJO-k

public class EmployeeDTO {private int workerId; privát String alkalmazottNév; // szerelők és beállítók}
public class Alkalmazott {private int id; privát karakterlánc neve; // szerelők és beállítók}

6.2. A Mapper felület

Különböző mezőnevek leképezésekor konfigurálnunk kell a forrásmezőt a célmezőhöz, és ehhez hozzá kell adnunk @Mappings annotáció. Ez a feljegyzés elfogadja a @Térképezés megjegyzés, amelyet a cél és a forrás attribútum hozzáadásához fogunk használni.

A MapStruct-ban pont jelöléssel is meghatározhatjuk a bab tagját:

@Mapper nyilvános felület EmployeeMapper {@Mappings ({@Mapping (target = "alkalmazottId", forrás = "entitás.id"), @Mapping (cél = "alkalmazottNév", forrás = "entitásnév")}) EmployeeDTO alkalmazottToEmployeeDTO ( Alkalmazotti jogalany); @Mappings ({@Mapping (target = "id", source = "dto.employeeId"), @Mapping (target = "név", source = "dto.employeeName")}) Employee workerDTOtoEmployee (EmployeeDTO dto); }

6.3. A teszteset

Ismét tesztelnünk kell, hogy mind a forrás, mind a cél objektum értéke megfelel-e:

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

További tesztesetek megtalálhatók a Github projektben.

7. Bab feltérképezése gyermek babokkal

Ezután megmutatjuk, hogyan lehet egy babot feltérképezni más babra hivatkozással.

7.1. Módosítsa a POJO-t

Adjunk hozzá egy új bab hivatkozást a Munkavállaló tárgy:

public class EmployeeDTO {private int workerId; privát String alkalmazottNév; magánosztályDTO részleg; // getterek és beállítók kihagyva}
public class Alkalmazott {private int id; privát karakterlánc neve; magánosztály részleg; // getterek és beállítók kihagyva}
public class Division {private int id; privát karakterlánc neve; // alapértelmezett konstruktor, getterek és beállítók kihagyva}

7.2. Módosítsa a Mappert

Itt hozzá kell adnunk egy módszert a Osztály nak nek DivisionDTO és fordítva; Ha a MapStruct észleli, hogy az objektumtípust konvertálni kell, és az átalakítandó módszer ugyanabban az osztályban létezik, akkor automatikusan használja.

Adjuk hozzá ezt a térképkészítőhöz:

DivisionDTO divízióToDivisionDTO (részleg entitás); Division divízióDTOtoDivision (DivisionDTO dto);

7.3. Módosítsa a tesztesetet

Módosítsunk és adjunk hozzá néhány tesztesetet a meglévőhöz:

@Test public void givenEmpDTONestedMappingToEmp_whenMaps_thenCorrect () {EmployeeDTO dto = new EmployeeDTO (); dto.setDivision (új DivisionDTO (1, "Division1")); Alkalmazott entitás = mapper.employeeDTOtoEmployee (dto); assertEquals (dto.getDivision (). getId (), entitás.getDivision (). getId ()); assertEquals (dto.getDivision (). getName (), entitás.getDivision (). getName ()); }

8. Térképezés típusátalakítással

A MapStruct néhány kész implicit típusú konverziót is kínál, és példánkként megpróbálunk egy String dátumot aktuálisra konvertálni Dátum tárgy.

Az implicit típusú konverzióval kapcsolatos további részletekért olvassa el a MapStruct referencia útmutatóját.

8.1. Módosítsa a babot

Adja meg munkatársunk kezdési dátumát:

public class Employee {// egyéb mezők private Dátum startDt; // szerelők és beállítók}
public class EmployeeDTO {// egyéb mezők private String workerStartDt; // szerelők és beállítók}

8.2. Módosítsa a Mappert

Módosítsa a leképezőt, és adja meg a dátum formátum kezdési dátumunkra:

@Mappings ({@Mapping (target = "alkalmazottId", forrás = "entitás.id"), @Mapping (cél = "alkalmazottNév", forrás = "entitásnév"), @Mapping (cél = "alkalmazottStartDt", forrás = "entitás.startDt", dateFormat = "éééé-hh-ééé ÓÓ: h: ss")}) EmployeeDTO workerToEmployeeDTO (Employee entitás); @Mappings ({@Mapping (target = "id", source = "dto.employeeId"), @Mapping (target = "név", source = "dto.employeeName"), @Mapping (target = "startDt", forrás = "dto.employeeStartDt", dateFormat = "dd-MM-yyyy HH: mm: ss")}) Munkavállalói alkalmazottDTOtoEmployee (MunkavállalóDTO dto);

8.3. Módosítsa a tesztesetet

Adjunk hozzá még néhány tesztesetet a konverzió helyes ellenőrzéséhez:

privát statikus végleges karakterlánc DATE_FORMAT = "éééé-hh-éééé ÓÓ: h: ss"; @Test public void givenEmpStartDtMappingToEmpDTO_whenMaps_thenCorrect () dobja a ParseException {Employee entitás = new Employee (); entitás.setStartDt (új dátum ()); EmployeeDTO dto = mapper.employeeToEmployeeDTO (entitás); SimpleDateFormat format = új SimpleDateFormat (DATE_FORMAT); assertEquals (format.parse (dto.getEmployeeStartDt ()). toString (), entitás.getStartDt (). toString ()); } @Test public void givenEmpDTOStartDtMappingToEmp_whenMaps_thenCorrect () dobja a ParseException {EmployeeDTO dto = new EmployeeDTO (); dto.setEmployeeStartDt ("2016-04-01 01:00:00"); Alkalmazott entitás = mapper.employeeDTOtoEmployee (dto); SimpleDateFormat format = új SimpleDateFormat (DATE_FORMAT); assertEquals (format.parse (dto.getEmployeeStartDt ()). toString (), entitás.getStartDt (). toString ()); }

9. Térképezés absztrakt osztállyal

Előfordulhat, hogy a térképkészítőnket úgy kell testre szabni, hogy az meghaladja a @Mapping képességeket.

Például a típusátalakítás mellett érdemes valamilyen módon átalakítani az értékeket, mint az alábbi példánkban.

Ebben az esetben létrehozhatunk egy absztrakt osztályt, és olyan módszereket valósíthatunk meg, amelyeket testre akarunk szabni, és absztraktakat hagyhatunk, amelyeket a MapStruct-nak kell generálnia.

9.1. Alapmodell

Ebben a példában a következő osztályt fogjuk használni:

public class Tranzakció {private Long id; privát karakterlánc uuid = UUID.randomUUID (). toString (); privát BigDecimal összesen; // standard getters}

és egy hozzá illő DTO:

public class TransactionDTO {private String uuid; privát Hosszú összesInCents; // szabványos mérőeszközök és beállítók}

A trükkös rész itt a BigDecimalteljesdollár összege a Hosszú összesInCents.

9.2. Mapper meghatározása

Ezt a sajátunk létrehozásával érhetjük el Mapper mint absztrakt osztály:

@Mapper absztrakt osztály TransactionMapper {public TransactionDTO toTransactionDTO (Tranzakciós tranzakció) {TransactionDTOactionDTO = új TransactionDTO (); ügyletDTO.setUuid (tranzakció.getUuid ()); actionDTO.setTotalInCents (tranzakció.getTotal () .multiply (új BigDecimal ("100")). longValue ()); visszatérési tranzakcióDTO; } public abstract list toTransactionDTO (Beszedési tranzakciók); }

Itt megvalósítottuk teljesen testreszabott leképezési módszerünket egyetlen objektumkonvertáláshoz.

Másrészt elhagytuk a feltérképezésre szánt módszert Gyűjteménya Listaelvont, tehát MapStruct végrehajtja nekünk.

9.3. Generált eredmény

Mivel már megvalósítottuk a módszert az egyes térképezésére Tranzakcióba be TransactionDTO, elvárjuk Mapstructhogy a második módszerben használja. A következők jönnek létre:

@Generated class TransactionMapperImpl kiterjeszti a TransactionMapper {@Override public list toTransactionDTO (Gyűjtési tranzakciók) {if (tranzakciók == null) {return null; } Lista lista = new ArrayList (); for (Tranzakciós tranzakció: tranzakciók) {list.add (toTransactionDTO (tranzakció)); } visszatérési lista; }}

Amint a 12. sorban láthatjuk, MapStruct implementációnkat az általa generált módszerben használja.

10. Megjegyzés előtti és utólagos feltérképezése

Itt van a testreszabás egy másik módja @Térképezés képességeinek használatával @BeforeMapping és @AfterMapping annotációk. A kommentárokkal olyan módszereket jelölhetünk meg, amelyeket közvetlenül a leképezési logika előtt és után hívunk meg.

Nagyon hasznosak olyan esetekben, amikor ezt szeretnénk az összes leképezett szuper-típusra alkalmazandó viselkedés.

Nézzünk meg egy példát, amely feltérképezi az altípusokat Autó; Elektromos autó, és BioDieselCar, nak nek CarDTO.

A feltérképezés során szeretnénk feltérképezni a típusok fogalmát a Üzemanyagtípus enum mező a DTO-ban, és miután a leképezés megtörtént, a DTO nevét nagybetűsre szeretnénk változtatni.

10.1. Alapmodell

Ebben a példában a következő osztályokat fogjuk használni:

public class Car {private int id; privát karakterlánc neve; }

Altípusai Autó:

a BioDieselCar nyilvános osztály meghosszabbítja az autót {}
Az ElectricCar nyilvános osztály meghosszabbítja az autót {}

A CarDTO enum mezőtípussal Üzemanyagtípus:

nyilvános osztály CarDTO {private int id; privát karakterlánc neve; privát FuelType FuelType; }
public enum FuelType {ELECTRIC, BIO_DIESEL}

10.2. A Mapper meghatározása

Most folytassuk, és írjuk meg absztrakt térképező osztályunkat, amely leképezi Autó nak nek CarDTO:

@Mapper public abstract class CarsMapper {@BeforeMapping protected void richDTOWithFuelType (Car car, @MappingTarget CarDTO carDto) {if (car car of ElectricCar) {carDto.setFuelType (FuelType.ELECTRIC); } if (a BioDieselCar autó példánya) {carDto.setFuelType (FuelType.BIO_DIESEL); }} @AfterMapping védett void convertNameToUpperCase (@MappingTarget CarDTO carDto) {carDto.setName (carDto.getName (). ToUpperCase ()); } nyilvános absztrakt CarDTO toCarDto (Autó autó); }

@MappingTarget egy paraméter annotáció, amely közvetlenül a leképezési logika végrehajtása előtt tölti be a cél leképező DTO-tesetében @BeforeMapping és után azonnal @AfterMapping annotált módszer.

10.3. Eredmény

A CarsMapper fent meghatározott generálavégrehajtás:

@Generated public class CarsMapperImpl kiterjeszti a CarsMapper {@Orride public CarDTO toCarDto (Car car) {if (car == null) {return null; } CarDTO carDTO = új CarDTO (); richDTOWithFuelType (autó, carDTO); carDTO.setId (car.getId ()); carDTO.setName (car.getName ()); convertNameToUpperCase (carDTO); visszatérő autóDTO; }}

Figyelje meg, hogyan az annotált metódusok invokációi veszik körül a leképezési logikát a megvalósításban.

11. Lombok támogatása

A MapStruct legújabb verziójában bejelentették a Lombok támogatását. Így könnyen feltérképezhetünk egy forrás entitást és egy úti célt a Lombok segítségével.

A Lombok támogatás engedélyezéséhez hozzá kell adnunk a függőséget az annotációs processzor útvonalán. Tehát most megvan a mapstruct-processzor és a Lombok is a Maven fordító beépülő moduljában:

 org.apache.maven.plugins maven-compiler-plugin 3.5.1 1.8 1.8 org.mapstruct mapstruct-processzor 1.3.1.Végleges org.projectlombok lombok 1.18.4 

Határozzuk meg a forrás entitást a Lombok annotációk segítségével:

@Getter @Setter public class Car {private int id; privát karakterlánc neve; }

És a cél adatátviteli objektum:

@Getter @Setter nyilvános osztály CarDTO {private int id; privát karakterlánc neve; }

Ennek leképező felülete hasonló marad az előző példánkhoz:

@Mapper nyilvános felület CarMapper {CarMapper INSTANCE = Mappers.getMapper (CarMapper.class); CarDTO carToCarDTO (autós autó); }

12. Támogatás defaultExpression

Az 1.3.0 verziótól kezdve, használhatjuk a defaultExpression attribútuma @Térképezés annotáció egy olyan kifejezés megadásához, amely meghatározza a célmező értékét, ha a forrásmező az nulla. Ez a meglévő mellett van alapértelmezett érték attribútum funkcionalitás.

A forrás entitás:

public class Személy {private int id; privát karakterlánc neve; }

A cél adatátviteli objektum:

nyilvános osztály PersonDTO {private int id; privát karakterlánc neve; }

Ha a id a forrás entitás mezője nulla, véletlenszerűt akarunk generálni id és rendelje hozzá a rendeltetési helyhez, megtartva a többi tulajdonságértéket:

@Mapper nyilvános felület PersonMapper {PersonMapper INSTANCE = Mappers.getMapper (PersonMapper.class); @Mapping (target = "id", source = "person.id", defaultExpression = "java (java.util.UUID.randomUUID (). ToString ())") PersonDTO personToPersonDTO (Személy személy); }

Adjunk hozzá egy tesztesetet a kifejezés végrehajtásának ellenőrzéséhez:

@Test public void givenPersonEntitytoPersonWithExpression_whenMaps_thenCorrect () Személy entitás = új Személy (); entitás.készletNév ("Micheal"); PersonDTO personDto = PersonMapper.INSTANCE.personToPersonDTO (entitás); assertNull (entitás.getId ()); assertNotNull (personDto.getId ()); assertEquals (personDto.getName (), entitás.getName ()); }

13. Következtetés

Ez a cikk bevezette a MapStructot. Bemutattuk a Mapping könyvtár legtöbb alapját, és azt, hogy miként lehet használni az alkalmazásainkban.

Ezen példák és tesztek megvalósítása megtalálható a Github projektben. Ez egy Maven projekt, ezért könnyen importálhatónak és futtathatónak kell lennie.