MapStruct : Transferring data from one bean to another
Converting data from one form to another is a highly utilized concept in IT industry. MapStruct allows annotation based bean conversion by generating mapper implementation at compile time. This makes sure there is no performance overhead at run-time.
What is MapStruct?
MapStruct is a code generator that greatly simplifies the implementation of mappings between Java bean types based on a convention over configuration approach.
The generated mapping code uses plain method invocations and thus is fast, type-safe and easy to understand.
Why MapStruct?
Multi-layered applications often require to map between different object models (e.g. entities and DTOs). Writing such mapping code is a tedious and error-prone task. MapStruct aims at simplifying this work by automating it as much as possible.
In contrast to other mapping frameworks MapStruct generates bean mappings at compile-time which ensures a high performance, allows for fast developer feedback and thorough error checking.
Implementation
pom.xml
In web.xml, add ” maven-compiler-plugin“, and with group id ” org.apache.maven.plugins“. You can add the specific jdk source/target version and get the latest version available from
MapStruct website.
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.5.1</version> <configuration> <source>1.6</source> <!-- or higher, depending on your project --> <target>1.6</target> <!-- or higher, depending on your project --> <annotationProcessorPaths> <path> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.1.0.Beta1</version> </path> </annotationProcessorPaths> </configuration> </plugin>
Now add the mapstruct jar as a dependency.
<dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>1.1.0.Beta1</version> </dependency>
Problem Statement and Solution
Suppose we have two pojos representing personal and business contacts as mentioned below and we are using both at specific jsps. Now for a functionality where both the contacts are same we need to transfer data from one pojo to another.
PrimaryContact.java
public class PrimaryContact { private String name; private String phone; private String email; public PrimaryContact() { super(); } public PrimaryContact(String name, String phone, String email) { super(); this.name = name; this.phone = phone; this.email = email; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } @Override public String toString() { return "PrimaryContact [name=" + name + ", phone=" + phone + ", email=" + email + "]"; } }
BusinessContact.java
public class BusinessContact { private String firstName; private String lastName; private String businessPhone; private String businessEmail; private String businessCountry; public BusinessContact() { super(); } public BusinessContact(String firstName, String lastName, String businessPhone, String businessEmail, String businessCountry) { super(); this.firstName = firstName; this.lastName = lastName; this.businessPhone = businessPhone; this.businessEmail = businessEmail; this.businessCountry = businessCountry; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getBusinessPhone() { return businessPhone; } public void setBusinessPhone(String businessPhone) { this.businessPhone = businessPhone; } public String getBusinessEmail() { return businessEmail; } public void setBusinessEmail(String businessEmail) { this.businessEmail = businessEmail; } public String getBusinessCountry() { return businessCountry; } public void setBusinessCountry(String businessCountry) { this.businessCountry = businessCountry; } @Override public String toString() { return "BusinessContact [firstName=" + firstName + ", lastName=" + lastName + ", businessPhone=" + businessPhone + ", businessEmail=" + businessEmail + ", businessCountry=" + businessCountry + "]"; } }
We write a Mapper to transfer the data as below. The annotation @Mappings define which attributes from source pojo will be transferred to specific attribute in target pojo. The annotation define that @InheritInverseConfiguration inverse mapping to be done.
ContactMapper.java
/** * @author javareferencegv */ @Mapper @DecoratedWith(ContactMapperDecorator.class) public interface ContactMapper { ContactMapper INSTANCE = Mappers.getMapper(ContactMapper.class); /** * We define only those mappings which doesn't have same signature in source and target */ @Mappings({ @Mapping(source = "phone", target = "businessPhone"), @Mapping(source = "email", target = "businessEmail"), @Mapping(target = "businessCountry", constant="USA") }) BusinessContact primaryToBusinessContact(PrimaryContact primary); @InheritInverseConfiguration PrimaryContact businessToPrimaryContact(BusinessContact business); }
There will be scenarios where mapping in not straight forward and we need custom logic before mapping one attribute to another. One such instance here is that primary contact has full name while business contact has first and last names. In such scenario we use a Decorator to add custom implementation. This is defined annotation @DecoratedWith adding in the mapper. The implementation for decorator is as below :
ContactMapperDecorator.java
public abstract class ContactMapperDecorator implements ContactMapper{ private final ContactMapper delegate; public ContactMapperDecorator(ContactMapper delegate) { this.delegate = delegate; } @Override public BusinessContact primaryToBusinessContact(PrimaryContact primary){ BusinessContact business = delegate.primaryToBusinessContact(primary); //Executes the mapper String[] names = primary.getName().split(" "); business.setFirstName(names[0]); business.setLastName(names[1]); return business; } @Override public PrimaryContact businessToPrimaryContact(BusinessContact business){ PrimaryContact primary = delegate.businessToPrimaryContact(business); //Executes the mapper primary.setName(business.getFirstName() + " " + business.getLastName()); return primary; } }
Execution :
Once we build an implementation class file will be generated by mapstruct. We are all set to run the mapper.:
public class ContactConvertor { public static void main(String[] args) { PrimaryContact primary = new PrimaryContact("Jack Sparrow","9999999999","test@javareferencegv.com"); BusinessContact business = ContactMapper.INSTANCE.primaryToBusinessContact(primary); System.out.println(business); PrimaryContact primaryConverted = ContactMapper.INSTANCE.businessToPrimaryContact(business); System.out.println(primaryConverted); } }
Output :
BusinessContact [firstName=Jack, lastName=Sparrow, businessPhone=9999999999, businessEmail=test@javareferencegv.com, businessCountry=USA] PrimaryContact [name=Jack Sparrow, phone=9999999999, email=test@javareferencegv.com]
Reference: | MapStruct : Transferring data from one bean to another from our JCG partner Gaurav Varma at the Java Reference blog. |