How to automate java bean to bean mapping in Spring Boot using MapStruct?
Let’s say you are developing an application which fetches data from the database.
Let’s say you are using hibernate to do the object relational data mapping.
So the data you fetch from database are mapped into Java entity objects .
And then you extract data from this entity object into another java bean object (DTO/data transfer object) as required by your client code.
Traditionally we have been doing this mapping manually using ‘modeler’ classes.
You use getters and setters and manually populate your DTOs from your entity objects.
This is time consuming and can be error prone.
Is there a way to automate this?
Using the library MapStruct.
You just need to create an interface where you tell MapStruct the mappings between the two java beans (This property in the first java bean should be mapped to that property in the second java bean) .
MapStruct then automatically populates those objects. It even does the type conversion for most cases by itself (Say one of the properties is an integer in the source java bean object but is a string in the destination java bean) .For cases where the type conversion fails you can tell Mapstruct how to convert it.
A basic example of how to automatically map source java bean values to destination java bean .
How to map properties with different names
How to map nested fields from source java bean to a property in destination java bean
How to update an already populated java bean with values from another java bean
Let’s dive into the implementation.
STEP1: Add MapStruct dependency to your project
STEP2: Create the source and destination java beans
STEP3: Create a mapper interface
STEP4: Run mvn clean install
STEP5: Test
Let’s look at this in detail.
STEP1: Add MapStruct dependency :
Add the below property, dependency and plugin details to your maven pom.xml:
is used to create an instance for the mapper which can later be used to convert the object.
VaccineDTO fromVaccine(Vaccine vaccine);
does the conversion automatically for us! You can give any name for the method .
STEP4: Run mvn clean install
Now we need to allow MapStruct to generate the ‘modeler’ classes automatically for us.
This is specified in pom.xml using the annotationProcessorPaths mapping.
For this to work you need to run mvn clean install or mvnw clean install in case you are using maven wrapper (Make sure JAVA_HOME is set in your environment variables for this to work)
After running this a class got generated automatically by MapStruct:
Here is the content of the above class:
package com.example.demo; import javax.annotation.processing.Generated; @Generated( value = "org.mapstruct.ap.MappingProcessor", date = "2021-05-31T17:26:50+0530", comments = "version: 1.4.2.Final, compiler: javac, environment: Java 16 (Oracle Corporation)" ) public class VaccineMapperImpl implements VaccineMapper < @Override public VaccineDTO fromVaccine(Vaccine vaccine) < if ( vaccine == null ) < return null; >VaccineDTO vaccineDTO = new VaccineDTO(); vaccineDTO.setName( vaccine.getName() ); vaccineDTO.setManufacturer( vaccine.getManufacturer() ); vaccineDTO.setQuantity( String.valueOf( vaccine.getQuantity() ) ); return vaccineDTO; > >
MapStruct created a modeler class for us!
And look at the line where the quantity is set to the vaccineDTO . It is converted to a string by MapStruct!
STEP5: Test
I created a test REST service like this:
package com.example.demo; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class TestCotroller < @GetMapping("/test") public VaccineDTO test() < //populated here for demo but assume this is fetched from db: Vaccine vaccine = new Vaccine(); vaccine.setName("Covaxin"); vaccine.setManufacturer("Adar and Co"); vaccine.setQuantity(100); VaccineMapper mapper = VaccineMapper.INSTANCE; VaccineDTO dto = mapper.fromVaccine(vaccine); return dto; >>
In the above code the Vaccine object is prepopulated (In a real case scenario this would be fetched from db).
And then I created an instance for the mapper and finally used it to map vaccine object values to vaccine dto object values.
Here is the output on calling the above REST service:
Now , what if the source and destination property names are different?
Let’s say in the vaccine class the property name of the manufacturer is ‘manufacturer’ but in the vaccine dto class it is ‘company’:
For this to work , add @Mapping annotation like below with the required source and destination names:
package com.example.demo; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class TestCotroller < @GetMapping("/test") public VaccineDTO test() < //populated here for demo but assume this is fetched from db: Vaccine vaccine = new Vaccine(); vaccine.setName("Covaxin"); vaccine.setManufacturer("Adar and Co"); vaccine.setQuantity(100); Dosage dosage = new Dosage(); dosage.setNoOfDosesRequired(2); dosage.setGapBetweenDoses(5); vaccine.setDosage(dosage); VaccineMapper mapper = VaccineMapper.INSTANCE; VaccineDTO dto = mapper.fromVaccine(vaccine); return dto; >>
Run mvn clean install again and test it:
Finally , lets see how to populate an already populated java bean with new values from another java bean (update)
There will be situations where you need to populate your DTO object with values from two or multiple entity objects.
In this case you first need to populate the values from the first entity bean as we had done in the previous steps .
Then you need to update the values from the second entity using @MappingTarget annotation.
Let’s create a new entity class named Location like below :