Snelle gids voor MapStruct

1. Overzicht

In dit artikel zullen we het gebruik van MapStruct onderzoeken, wat simpel gezegd een Java Bean-mapper is.

Deze API bevat functies die automatisch mappen tussen twee Java Beans. Met MapStruct hoeven we alleen de interface te maken en de bibliotheek zal automatisch een concrete implementatie creëren tijdens het compileren.

2. MapStruct en Transfer Object Pattern

Voor de meeste toepassingen zul je veel standaardcode opmerken die POJO's omzet in andere POJO's.

Een veelvoorkomend type conversie vindt bijvoorbeeld plaats tussen door persistentie gesteunde entiteiten en DTO's die naar de klant gaan.

Dus dat is het probleem dat MapStruct oplosthet handmatig maken van bean-mappers is tijdrovend. De bibliotheek kan automatisch bean-mapper-klassen genereren.

3. Maven

Laten we de onderstaande afhankelijkheid toevoegen aan onze Maven pom.xml:

 org.mapstruct mapstruct 1.3.1.Final 

De laatste stabiele uitgave van Mapstruct en zijn processor zijn beide beschikbaar in de Maven Central Repository.

Laten we ook de annotationProcessorPaths sectie naar het configuratiegedeelte van het maven-compiler-plugin inpluggen.

De mapstruct-processor wordt gebruikt om de mapper-implementatie te genereren tijdens de build:

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

4. Basiskartering

4.1. Een POJO maken

Laten we eerst een eenvoudige Java POJO maken:

openbare klasse SimpleSource {private String-naam; private String beschrijving; // getters en setters} openbare klasse SimpleDestination {private String-naam; private String beschrijving; // getters en setters}

4.2. De Mapper-interface

@Mapper openbare interface SimpleSourceDestinationMapper {SimpleDestination sourceToDestination (SimpleSource-bron); SimpleSource destinationToSource (SimpleDestination-bestemming); }

Merk op dat we geen implementatieklasse hebben gemaakt voor onze SimpleSourceDestinationMapper - omdat MapStruct het voor ons maakt.

4.3. De nieuwe kaartenmaker

We kunnen de MapStruct-verwerking activeren door een mvn schone installatie.

Dit genereert de implementatieklasse onder / target / gegenereerde-bronnen / annotaties /.

Dit is de klasse die MapStruct automatisch voor ons aanmaakt:

openbare klasse SimpleSourceDestinationMapperImpl implementeert SimpleSourceDestinationMapper {@Override openbare SimpleDestination sourceToDestination (SimpleSource-bron) {if (source == null) {return null; } SimpleDestination simpleDestination = nieuwe SimpleDestination (); simpleDestination.setName (source.getName ()); simpleDestination.setDescription (source.getDescription ()); retourneer simpleDestination; } @Override openbare SimpleSource destinationToSource (SimpleDestination-bestemming) {if (destination == null) {return null; } SimpleSource simpleSource = nieuwe SimpleSource (); simpleSource.setName (bestemming.getnaam ()); simpleSource.setDescription (bestemming.getDescription ()); retourneer simpleSource; }}

4.4. Een testcase

Laten we tot slot, met alles gegenereerd, een testcase schrijven om te laten zien dat de waarden in SimpleSource match waarden in SimpleDestination.

openbare klasse SimpleSourceDestinationMapperIntegrationTest {privé SimpleSourceDestinationMapper mapper = Mappers.getMapper (SimpleSourceDestinationMapper.class); @Test openbare leegte gegevenSourceToDestination_whenMaps_thenCorrect () {SimpleSource simpleSource = nieuwe SimpleSource (); simpleSource.setName ("Bronnaam"); simpleSource.setDescription ("SourceDescription"); SimpleDestination bestemming = mapper.sourceToDestination (simpleSource); assertEquals (simpleSource.getName (), destination.getName ()); assertEquals (simpleSource.getDescription (), destination.getDescription ()); } @Test openbare leegte gegevenDestinationToSource_whenMaps_thenCorrect () {SimpleDestination bestemming = nieuwe SimpleDestination (); bestemming.setnaam ("Bestemmingsnaam"); bestemming.setDescription ("DestinationDescription"); SimpleSource source = mapper.destinationToSource (bestemming); assertEquals (bestemming.getnaam (), bron.getnaam ()); assertEquals (bestemming.getDescription (), source.getDescription ()); }}

5. In kaart brengen met afhankelijkheidsinjectie

Laten we vervolgens een instantie van een mapper in MapStruct verkrijgen door alleen maar aan te roepen Mappers.getMapper (YourClass.class).

Dat is natuurlijk een zeer handmatige manier om de instantie te krijgen - een veel beter alternatief zou zijn om de mapper direct daar te injecteren waar we deze nodig hebben (als ons project een Dependency Injection-oplossing gebruikt).

Gelukkig heeft MapStruct solide ondersteuning voor zowel Spring als CDI (Contexten en afhankelijkheidsinjectie).

Om Spring IoC in onze mapper te gebruiken, moeten we de componentModeltoe te schrijven aan @Mapper met de waarde voorjaar en voor CDI zou zijn cdi .

5.1. Wijzig de Mapper

Voeg de volgende code toe aan SimpleSourceDestinationMapper:

@Mapper (componentModel = "spring") openbare interface SimpleSourceDestinationMapper

6. Velden met verschillende veldnamen in kaart brengen

In ons vorige voorbeeld kon MapStruct onze bonen automatisch in kaart brengen omdat ze dezelfde veldnamen hebben. Dus wat als een boon die we op het punt staan ​​in kaart te brengen een andere veldnaam heeft?

Voor ons voorbeeld gaan we een nieuwe boon maken met de naam Werknemer en MedewerkerDTO.

6.1. Nieuwe POJO's

openbare klasse EmployeeDTO {private int employeeId; private String employeeName; // getters en setters}
openbare klasse Medewerker {privé int id; private String naam; // getters en setters}

6.2. De Mapper-interface

Bij het toewijzen van verschillende veldnamen, moeten we het bronveld configureren voor het doelveld en daarvoor moeten we toevoegen @Mappings annotatie. Deze annotatie accepteert een reeks @In kaart brengen annotatie die we zullen gebruiken om het doel- en bronkenmerk toe te voegen.

In MapStruct kunnen we ook puntnotatie gebruiken om een ​​lid van een boon te definiëren:

@Mapper openbare interface EmployeeMapper {@Mappings ({@Mapping (target = "employeeId", source = "entity.id"), @Mapping (target = "employeeName", source = "entity.name")}) EmployeeDTO employeeToEmployeeDTO ( Werknemersentiteit); @Mappings ({@Mapping (target = "id", source = "dto.employeeId"), @Mapping (target = "name", source = "dto.employeeName")}) Werknemer employeeDTOtoEmployee (EmployeeDTO dto); }

6.3. De testcase

We moeten opnieuw testen of zowel de bron- als de doelobjectwaarden overeenkomen:

@Test openbare ongeldige gegevenEmployeeDTOwithDiffNametoEmployee_whenMaps_thenCorrect () {EmployeeDTO dto = nieuwe EmployeeDTO (); dto.setEmployeeId (1); dto.setEmployeeName ("John"); Werknemerentiteit = mapper.employeeDTOtoEmployee (dto); assertEquals (dto.getEmployeeId (), entity.getId ()); assertEquals (dto.getEmployeeName (), entity.getName ()); }

Meer testcases zijn te vinden in het Github-project.

7. Bonen met kinderbonen in kaart brengen

Vervolgens laten we zien hoe u een boon kunt toewijzen aan verwijzingen naar andere bonen.

7.1. Wijzig het POJO

Laten we een nieuwe bonenverwijzing toevoegen aan het Werknemer voorwerp:

openbare klasse EmployeeDTO {private int employeeId; private String employeeName; particuliere divisieDTO-divisie; // getters en setters weggelaten}
openbare klasse Medewerker {privé int id; private String naam; particuliere divisie; // getters en setters weggelaten}
openbare klasse Divisie {privé int id; private String naam; // standaard constructor, getters en setters weggelaten}

7.2. Wijzig de Mapper

Hier moeten we een methode toevoegen om het Divisie naar DivisieDTO en vice versa; als MapStruct detecteert dat het objecttype moet worden geconverteerd en de methode om te converteren bestaat in dezelfde klasse, dan zal het deze automatisch gebruiken.

Laten we dit toevoegen aan de mapper:

DivisionDTO divisionToDivisionDTO (Division-entiteit); Divisie divisieDTOtoDivisie (DivisieDTO dto);

7.3. Wijzig de testcase

Laten we een paar testcases aanpassen en aan de bestaande toevoegen:

@ Test openbare ongeldige gegevenEmpDTONestedMappingToEmp_whenMaps_thenCorrect () {EmployeeDTO dto = nieuwe EmployeeDTO (); dto.setDivision (nieuwe DivisionDTO (1, "Division1")); Werknemerentiteit = mapper.employeeDTOtoEmployee (dto); assertEquals (dto.getDivision (). getId (), entity.getDivision (). getId ()); assertEquals (dto.getDivision (). getName (), entity.getDivision (). getName ()); }

8. Mapping met typeconversie

MapStruct biedt ook een aantal kant-en-klare impliciete typeconversies, en voor ons voorbeeld zullen we proberen een String-datum om te zetten in een werkelijke Datum voorwerp.

Voor meer informatie over impliciete typeconversie kunt u de MapStruct-naslaggids lezen.

8.1. Pas de bonen aan

Voeg een startdatum toe voor onze medewerker:

openbare klasse Medewerker {// andere velden privé Datum startDt; // getters en setters}
openbare klasse EmployeeDTO {// andere velden privé String employeeStartDt; // getters en setters}

8.2. Wijzig de Mapper

Wijzig de mapper en geef het datumnotatie voor onze startdatum:

@Mappings ({@Mapping (target = "employeeId", source = "entity.id"), @Mapping (target = "employeeName", source = "entity.name"), @Mapping (target = "employeeStartDt", source = "entity.startDt", dateFormat = "dd-MM-yyyy HH: mm: ss")}) EmployeeDTO employeeToEmployeeDTO (Employee entiteit); @Mappings ({@Mapping (target = "id", source = "dto.employeeId"), @Mapping (target = "name", source = "dto.employeeName"), @Mapping (target = "startDt", bron = "dto.employeeStartDt", dateFormat = "dd-MM-jjjj UU: mm: ss")}) Werknemer werknemerDTOtoEmployee (EmployeeDTO dto);

8.3. Wijzig de testcase

Laten we nog een paar testcases toevoegen om te controleren of de conversie correct is:

private static final String DATE_FORMAT = "dd-MM-jjjj UU: mm: ss"; @Test openbare leegte gegevenEmpStartDtMappingToEmpDTO_whenMaps_thenCorrect () gooit ParseException {Employee entity = new Employee (); entiteit.setStartDt (nieuwe datum ()); EmployeeDTO dto = mapper.employeeToEmployeeDTO (entiteit); SimpleDateFormat-indeling = nieuwe SimpleDateFormat (DATE_FORMAT); assertEquals (format.parse (dto.getEmployeeStartDt ()). toString (), entity.getStartDt (). toString ()); } @Test openbare leegte gegevenEmpDTOStartDtMappingToEmp_whenMaps_thenCorrect () gooit ParseException {EmployeeDTO dto = nieuwe EmployeeDTO (); dto.setEmployeeStartDt ("01-04-2016 01:00:00"); Werknemerentiteit = mapper.employeeDTOtoEmployee (dto); SimpleDateFormat-indeling = nieuwe SimpleDateFormat (DATE_FORMAT); assertEquals (format.parse (dto.getEmployeeStartDt ()). toString (), entity.getStartDt (). toString ()); }

9. In kaart brengen met een abstracte klasse

Soms willen we onze mapper aanpassen op een manier die de @Mapping-mogelijkheden overtreft.

Naast typeconversie kunnen we bijvoorbeeld de waarden op de een of andere manier willen transformeren, zoals in ons voorbeeld hieronder.

In dat geval kunnen we een abstracte klasse maken en methoden implementeren die we op maat willen hebben, en die abstract laten, die door MapStruct moeten worden gegenereerd.

9.1. Basismodel

In dit voorbeeld gebruiken we de volgende klasse:

openbare klasse Transactie {privé Lange id; private String uuid = UUID.randomUUID (). toString (); privé BigDecimal totaal; // standaard getters}

en een bijpassende DTO:

openbare klasse TransactionDTO {private String uuid; privé Long totalInCents; // standaard getters en setters}

Het lastige hier is het converteren van de BigDecimaltotaalhoeveelheid dollars in een Lange totaalIncenten.

9.2. Een kaartenmaker definiëren

We kunnen dit bereiken door onze Mapper als een abstracte klasse:

@Mapper abstracte klasse TransactionMapper {openbare TransactionDTO toTransactionDTO (Transactietransactie) {TransactionDTO transactionDTO = nieuwe TransactionDTO (); transactionDTO.setUuid (transaction.getUuid ()); transactionDTO.setTotalInCents (transaction.getTotal () .multiply (new BigDecimal ("100")). longValue ()); retourtransactieDTO; } openbare samenvattingslijst toTransactionDTO (incassotransacties); }

Hier hebben we onze volledig aangepaste toewijzingsmethode geïmplementeerd voor conversie van één object.

Aan de andere kant hebben we de methode verlaten die bedoeld is om in kaart te brengen Verzamelingnaar een Lijstabstract, dus MapStruct zal het voor ons implementeren.

9.3. Gegenereerd resultaat

Omdat we de methode om single in kaart te brengen al hebben geïmplementeerd Transactiein een TransactionDTO, wij verwachten Mapstructom het in de tweede methode te gebruiken. Het volgende wordt gegenereerd:

@Generated klasse TransactionMapperImpl breidt TransactionMapper {@Override openbare lijst uit naarTransactionDTO (incasso-transacties) {if (transacties == null) {return null; } Lijst lijst = nieuwe ArrayList (); voor (Transactie transactie: transacties) {list.add (toTransactionDTO (transactie)); } retourlijst; }}

Zoals we kunnen zien in regel 12, MapStruct gebruikt onze implementatie in de methode die het heeft gegenereerd.

10. Annotaties vóór het in kaart brengen en na het in kaart brengen

Hier is een andere manier om aan te passen @In kaart brengen mogelijkheden door gebruik te maken van @BeforeMapping en @AfterMapping annotaties. De annotaties worden gebruikt om methoden te markeren die vlak voor en na de mappinglogica worden aangeroepen.

Ze zijn erg handig in scenario's waarin we dit misschien willen gedrag dat moet worden toegepast op alle in kaart gebrachte supertypen.

Laten we eens kijken naar een voorbeeld dat de subtypes van Auto; Elektrische auto, en BioDieselCar, naar CarDTO.

Bij het in kaart brengen willen we het begrip typen in kaart brengen in de Brandstoftype enum veld in de DTO, en nadat de mapping is voltooid, willen we de naam van de DTO veranderen in hoofdletters.

10.1. Basismodel

In dit voorbeeld gebruiken we de volgende klassen:

openbare klasse Car {privé int id; private String naam; }

Subtypen van Auto:

openbare klasse BioDieselCar breidt auto uit {}
openbare klasse ElectricCar breidt auto uit {}

De CarDTO met een enum-veldtype Brandstoftype:

openbare klasse CarDTO {privé int id; private String naam; privé FuelType fuelType; }
openbare opsomming FuelType {ELECTRIC, BIO_DIESEL}

10.2. De Mapper definiëren

Laten we nu verder gaan en onze abstracte mapper-klasse schrijven, die kaarten Auto naar CarDTO:

@Mapper openbare abstracte klasse CarsMapper {@BeforeMapping beschermde leegte enrichDTOWithFuelType (Car car, @MappingTarget CarDTO carDto) {if (car instanceof ElectricCar) {carDto.setFuelType (FuelType.ELECTRIC); } if (auto-exemplaar van BioDieselCar) {carDto.setFuelType (FuelType.BIO_DIESEL); }} @AfterMapping beschermde ongeldige convertNameToUpperCase (@MappingTarget CarDTO carDto) {carDto.setName (carDto.getName (). ToUpperCase ()); } openbare samenvatting CarDTO toCarDto (Car car); }

@MappingTarget is een parameterannotatie die vult de doeldoewijzings-DTO in vlak voordat de toewijzingslogica wordt uitgevoerdin het geval van @BeforeMapping en direct daarna in het geval van @AfterMapping geannoteerde methode.

10.3. Resultaat

De CarsMapper hierboven gedefinieerd genereertdeimplementatie:

@Generated public class CarsMapperImpl breidt CarsMapper {@Override public CarDTO uit naarCarDto (Car car) {if (car == null) {return null; } CarDTO carDTO = nieuwe CarDTO (); enrichDTOWithFuelType (auto, autoDTO); carDTO.setId (car.getId ()); carDTO.setName (car.getName ()); convertNameToUpperCase (carDTO); terug autoDTO; }}

Merk op hoe de geannoteerde methodeaanroepen omringen de afbeeldingslogica bij de uitvoering.

11. Ondersteuning voor Lombok

In de recente versie van MapStruct werd Lombok-ondersteuning aangekondigd. We kunnen dus eenvoudig een bronentiteit en een bestemming in kaart brengen met behulp van Lombok.

Om Lombok-ondersteuning mogelijk te maken, moeten we de afhankelijkheid toevoegen aan het pad van de annotatieprocessor. Dus nu hebben we zowel de mapstruct-processor als Lombok in de Maven-compiler-plug-in:

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

Laten we de bronentiteit definiëren met behulp van Lombok-annotaties:

@Getter @Setter openbare klasse Auto {privé int id; private String naam; }

En het doelgegevensoverdrachtsobject:

@Getter @Setter openbare klasse CarDTO {privé int id; private String naam; }

De mapper-interface hiervoor blijft vergelijkbaar met ons vorige voorbeeld:

@Mapper openbare interface CarMapper {CarMapper INSTANCE = Mappers.getMapper (CarMapper.class); CarDTO carToCarDTO (auto-auto); }

12. Ondersteuning voor defaultExpression

Vanaf versie 1.3.0, we kunnen de defaultExpression attribuut van de @In kaart brengen annotatie om een ​​uitdrukking op te geven die de waarde van het bestemmingsveld bepaalt als het bronveld is nul. Dit komt bovenop het bestaande standaardwaarde kenmerk functionaliteit.

De bronentiteit:

openbare klasse Persoon {privé int id; private String naam; }

Het doelgegevensoverdrachtobject:

openbare klasse PersonDTO {privé int id; private String naam; }

Als het ID kaart veld van de bronentiteit is nul, we willen een random genereren ID kaart en wijs het toe aan de bestemming met behoud van andere eigenschapswaarden zoals ze zijn:

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

Laten we een testcase toevoegen om de uitvoering van de expressie te verifiëren:

@Test openbare ongeldig gegevenPersonEntitytoPersonWithExpression_whenMaps_thenCorrect () Persoon entiteit = nieuwe persoon (); entity.setName ("Micheal"); PersonDTO personDto = PersonMapper.INSTANCE.personToPersonDTO (entiteit); assertNull (entiteit.getId ()); assertNotNull (personDto.getId ()); assertEquals (personDto.getName (), entity.getName ()); }

13. Conclusie

Dit artikel bevatte een inleiding tot MapStruct. We hebben de meeste basisprincipes van de mappingbibliotheek geïntroduceerd en hoe u deze in onze applicaties kunt gebruiken.

De implementatie van deze voorbeelden en tests is te vinden in het Github-project. Dit is een Maven-project, dus het moet gemakkelijk te importeren en uit te voeren zijn zoals het is.


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