Core Java

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.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
<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.

1
2
3
4
5
<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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
/**
 * @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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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.:

01
02
03
04
05
06
07
08
09
10
11
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 :

1
2
BusinessContact [firstName=Jack, lastName=Sparrow, businessPhone=9999999999, businessEmail=test@javareferencegv.com, businessCountry=USA]
PrimaryContact [name=Jack Sparrow, phone=9999999999, email=test@javareferencegv.com]

Gaurav Varma

J2EE enthusiast and engineer since 2008. Worked on multiple J2EE technologies. Currently concentrating on RESTful Web services and Core Java.
Subscribe
Notify of
guest


This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button