Quantcast
Channel: Baeldung
Viewing all articles
Browse latest Browse all 4536

Intro to Mapping with MapStruct

$
0
0

The Master Class of "Learn Spring Security" is live:

>> CHECK OUT THE COURSE

1. Overview

In this article, we’ll explore the use of MapStruct which is, simply put, a Java Bean mapper.

This API contains functions that automatically map between two Java Beans. With MapStruct we only need to create the interface and the library will automatically create a concrete implementation during compile time.

2. MapStruct and Transfer Object Pattern

For most applications, you’ll notice a lot of boilerplate code converting POJOs to other POJOs.

For example, a common type of conversion happens between persistence-backed entities and DTOs that go out to the client side.

So that is the problem that MapStruct solvesmanually creating bean mappers is time-consuming. The library is able to generate bean mapper classes automatically.

3. Maven

Let’s add the below dependency into our Maven pom.xml:

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-jdk8</artifactId>
    <version>1.0.0.Final</version> 
</dependency>

The latest stable release can always be found here:

Let’s also add the annotationProcessorPaths section to the configuration part of the maven-compiler-plugin plugin. The mapstruct-processor is used to generate the mapper implementation during the build:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.5.1</version>
    <configuration>
        <source>1.8</source>
	<target>1.8</target>
        <annotationProcessorPaths>
            <path>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct-processor</artifactId>
                <version>${org.mapstruct.version}</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>

4. Basic Mapping

4.1. Creating a POJO

Let’s first create a simple Java POJO:

public class SimpleSource {

    private String name;
    private String description;

    // getters and setters
}
 
public class SimpleDestination {

    private String name;
    private String description;

    // getters and setters
}

4.2. The Mapper Interface

@Mapper
public interface SimpleSourceDestinationMapper {
    SimpleDestination sourceToDestination(SimpleSource source);
    SimpleSource destinationToSource(SimpleDestination destination);
}

Notice we did not create any implementation class for our SimpleSourceDestinationMapper – because MapStruct creates it for us.

4.3. The New Mapper

We can trigger the MapStruct processing by executing an mvn clean install.

This will generate the implementation class under /target/generated-sources/annotations/.

Here is the class that MapStruct auto-creates for us:

public class SimpleSourceDestinationMapperImpl
  implements SimpleSourceDestinationMapper {

    @Override
    public SimpleDestination sourceToDestination(SimpleSource source) {
        if ( source == null ) {
            return null;
        }

        SimpleDestination simpleDestination = new SimpleDestination();

        simpleDestination.setName( source.getName() );
        simpleDestination.setDescription( source.getDescription() );

        return simpleDestination;
    }

    @Override
    public SimpleSource destinationToSource(SimpleDestination destination){
        if ( destination == null ) {
            return null;
        }

        SimpleSource simpleSource = new SimpleSource();

        simpleSource.setName( destination.getName() );
        simpleSource.setDescription( destination.getDescription() );

        return simpleSource;
    }
}

4.4. A Test Case

Finally, with everything generated, let’s write a test case will show that values in SimpleSource match values in SimpleDestination.

public class SimpleSourceDestinationMapperTest {

    private SimpleSourceDestinationMapper mapper
      = Mappers.getMapper(SimpleSourceDestinationMapper.class);

    @Test
    public void givenSourceToDestination_whenMaps_thenCorrect() {
        SimpleSource simpleSource = new SimpleSource();
        simpleSource.setName("SourceName");
        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("DestinationName");
        destination.setDescription("DestinationDescription");

        SimpleSource source = mapper.destinationToSource(destination);

        assertEquals(destination.getName(), source.getName());
        assertEquals(destination.getDescription(),
          source.getDescription());
    }
}

5. Mapping with Dependency Injection

Next, let’s obtain an instance of a mapper in MapStruct by simply calling Mappers.getMapper(YourClass.class).

Of course that’s a very manual way of getting the instance – a much better alternative would be injecting the mapper directly where we need it (if our project uses any Dependency Injection solution).

Luckily MapStruct has solid support for both Spring and CDI (Contexts and Dependency Injection).

To use Spring IoC in our mapper, we simply need to add the componentModel attribute to @Mapper with the value spring and for CDI would be cdi .

5.1. Modify the Mapper

Add the following code to SimpleSourceDestinationMapper:

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

6. Mapping Fields with Different Field Names

From our previous example, MapStruct was able to map our beans automatically because they have the same field names. So what if a bean we are about to map has a different field name?

For our example, we will be creating a new bean called Employee and EmployeeDTO.

6.1. New POJOs

public class EmployeeDTO {

    private int employeeId;
    private String employeeName;

    // getters and setters
}
public class Employee {

    private int id;
    private String name;

    // getters and setters
}

6.2. The Mapper Interface

When mapping different field names, we will need to configure its source field to its target field and to do that we will need to add @Mappings annotation. This annotation accepts an array of @Mapping annotation which we will use to add the target and source attribute.

In MapStruct we can also use dot notation to define a member of a bean:

@Mapper
public interface EmployeeMapper {

    @Mappings({
      @Mapping(target="employeeId", source="entity.id"),
      @Mapping(target="employeeName", source="entity.name")
    })
    EmployeeDTO employeeToEmployeeDTO(Employee entity);

    @Mappings({
      @Mapping(target="id", source="dto.employeeId"),
      @Mapping(target="name", source="dto.employeeName")
    })
    Employee employeeDTOtoEmployee(EmployeeDTO dto);
}

6.3. The Test Case

Again we need to test that both source and destination object values match:

@Test
public void givenEmpDTODiffNametoEmp_whenMaps_thenCorrect() {
    EmployeeDTO dto = new EmployeeDTO();
    dto.setEmployeeId(1);
    dto.setEmployeeName("John");

    Employee entity = mapper.employeeDTOtoEmployee(dto);

    assertEquals(dto.getEmployeeId(), entity.getId());
    assertEquals(dto.getEmployeeName(), entity.getName());
}

More test cases can be found in the Github project.

7. Mapping Beans with Child Beans

Next we’ll show how to map a bean with references to other beans.

7.1. Modify the POJO

Let’s add a new bean reference in the Employee object:

public class EmployeeDTO {

    private int employeeId;
    private String employeeName;
    private DivisionDTO division;

    // getters and setters omitted
}
public class Employee {

    private int id;
    private String name;
    private Division division;

    // getters and setters omitted
}
public class Division {

    private int id;
    private String name;

    // default constructor, getters and setters omitted
}

7.2. Modify the Mapper

Here we need to add a method to convert the Division to DivisionDTO and vice versa; if MapStruct detects that the object type needs to be converted and the method to convert exists in the same class then it will use it automatically.

Let’s add this to the mapper:

DivisionDTO divisionToDivisionDTO(Division entity);

Division divisionDTOtoDivision(DivisionDTO dto);

7.3. Modify the Test Case

Let’s modify and add a few test cases to the existing one:

@Test
public void givenEmpDTONestedMappingToEmp_whenMaps_thenCorrect() {
    EmployeeDTO dto = new EmployeeDTO();
    dto.setDivision(new DivisionDTO(1, "Division1"));

    Employee entity = mapper.employeeDTOtoEmployee(dto);

    assertEquals(dto.getDivision().getId(), 
      entity.getDivision().getId());
    assertEquals(dto.getDivision().getName(), 
      entity.getDivision().getName());
}

8. Mapping With Type Conversion

MapStruct also offers a couple of ready made implicit type conversions and for our example, we will try to convert a String date to an actual Date object.

For more details on implicit type conversion, you may read the MapStruct reference guide.

8.1. Modify the Beans

Add a start date for our employee:

public class Employee {

    // other fields
    private Date startDt;

    // getters and setters
}
public class EmployeeDTO {

    // other fields
    private String employeeStartDt;

    // getters and setters
}

8.2. Modify the Mapper

Modify the mapper and provide the dateFormat for our start date:

@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 entity);

@Mappings({
  @Mapping(target="id", source="dto.employeeId"),
  @Mapping(target="name", source="dto.employeeName"),
  @Mapping(target="startDt", source="dto.employeeStartDt",
           dateFormat="dd-MM-yyyy HH:mm:ss")})
Employee employeeDTOtoEmployee(EmployeeDTO dto);

8.3. Modify the Test Case

Let’s add a few more test case to verify the conversion is correct:

private static final String DATE_FORMAT = "dd-MM-yyyy HH:mm:ss";

@Test
public void givenEmpStartDtMappingToEmpDTO_whenMaps_thenCorrect() throws ParseException {
    Employee entity = new Employee();
    entity.setStartDt(new Date());

    EmployeeDTO dto = mapper.employeeToEmployeeDTO(entity);
    SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT);
 
    assertEquals(format.parse(dto.getEmployeeStartDt()).toString(),
      entity.getStartDt().toString());
}

@Test
public void givenEmpDTOStartDtMappingToEmp_whenMaps_thenCorrect() throws ParseException {
    EmployeeDTO dto = new EmployeeDTO();
    dto.setEmployeeStartDt("01-04-2016 01:00:00");

    Employee entity = mapper.employeeDTOtoEmployee(dto);
    SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT);
 
    assertEquals(format.parse(dto.getEmployeeStartDt()).toString(),
      entity.getStartDt().toString());
}

9. Conclusion

This article provided an introduction to MapStruct, we have introduced most of the basics of the Mapping library and how to use it in our applications.

The implementation of these examples and tests can be found in the Github project. This is a Maven project, so it should be easy to import and run as it is.

I just released the Master Class of "Learn Spring Security" Course:

>> CHECK OUT THE COURSE


Viewing all articles
Browse latest Browse all 4536

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>