Skip to content

Latest commit

 

History

History
375 lines (244 loc) · 11.9 KB

File metadata and controls

375 lines (244 loc) · 11.9 KB

MapStruct

MapStruct is a code generator that greatly simplifies the implementation of mappings between Java bean types based on a convention over configuration approach. It is a Java annotation processor for the generation of type-safe bean mapping classes.

All you have to do is to define a mapper interface which declares any required mapping methods. During compilation, MapStruct will generate an implementation of this interface. This implementation uses plain Java method invocations for mapping between source and target objects, i.e. no reflection or similar.


Set up

Maven

Add the following to your POM file in order to use MapStruct:

...
<properties>
    <org.mapstruct.version>1.2.0.Final</org.mapstruct.version>
</properties>
...
<dependencies>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct-jdk8</artifactId>
        <version>${org.mapstruct.version}</version>
    </dependency>
</dependencies>
...
<build>
    <plugins>
        <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>
    </plugins>
</build>
...

Gradle

...
plugins {
    ...
    id 'net.ltgt.apt' version '0.8'
}
dependencies {
    ...
    compile 'org.mapstruct:mapstruct-jdk8:1.2.0.Final'

    apt 'org.mapstruct:mapstruct-processor:1.2.0.Final'
}
...

Configuration

The MapStruct code generator can be configured using annotation processor options.

When invoking javac directly, these options are passed to the compiler in the form -Akey=value. When using MapStruct via Maven, any processor options can be passed using an options element within the configuration of the Maven processor like this.


Starting up with MapStruct

  • Create Source and Destination classes

  • Define mapper interface with methods declaration for mapping.

  • do mvn clean install.


MapStruct and lombok have intermittent issues currently.

Maven only uses the MapStruct processor and not the Lombok one. The annotationProcessorPaths tells maven which processors it should use.

Add the lombok dependency in the annotationProcessorPaths

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

Defining a mapper

To create a mapper simply define a Java interface with the required mapping method(s) and annotate it with the org.mapstruct.Mapper annotation:

@Mapper
public interface CarMapper {

    @Mappings({
        @Mapping(source = "make", target = "manufacturer"),
        @Mapping(source = "numberOfSeats", target = "seatCount")
    })
    CarDto carToCarDto(Car car);

    @Mapping(source = "name", target = "fullName")
    PersonDto personToPersonDto(Person person);
}

The @Mapper annotation causes the MapStruct code generator to create an implementation of the CarMapper interface during build-time.

  • If a property has a different name in the target entity, its name can be specified via the @Mapping annotation.

  • If the type of a mapped attribute is different in source and target entity, MapStruct will either apply an automatic conversion or optionally invoke / create another mapping method.

When using Java 8 or later, you can omit the @Mappings wrapper annotation and directly specify several @Mapping annotations on one method.


Adding custom methods to mappers

In some cases it can be required to manually implement a specific mapping from one type to another which can’t be generated by MapStruct. One way for this is to implement such method on another class which then is used by mappers generated by MapStruct. Alternatively, when using Java 8 or later, you can implement custom methods directly in a mapper interface as default methods. The generated code will invoke the default methods if the argument and return types match.

@Mapper
public interface CarMapper {

    @Mappings({...})
    CarDto carToCarDto(Car car);

    default PersonDto personToPersonDto(Person person) {
        //hand-written mapping logic
    }
}

The generated code in carToCarDto() will invoke the manually implemented personToPersonDto() method when mapping the driver attribute.

A mapper could also be defined in form of an abstract class instead of an interface and implement custom methods directly in this mapper class. In this case MapStruct will generate an extension of the abstract class with implementations of all abstract methods. An advantage of this approach over declaring default methods is that additional fields could be declared in the mapper class.

@Mapper
public abstract class CarMapper {

    @Mappings(...)
    public abstract CarDto carToCarDto(Car car);

    public PersonDto personToPersonDto(Person person) {
        //hand-written mapping logic
    }
}

Mapping methods with several source parameters

MapStruct also supports mapping methods with several source parameters. This is useful e.g. in order to combine several entities into one data transfer object.

@Mapper
public interface AddressMapper {

    @Mappings({
        @Mapping(source = "person.description", target = "description"),
        @Mapping(source = "address.houseNo", target = "houseNumber")
    })
    DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address);
}

MapStruct also offers the possibility to directly refer to a source parameter.

@Mapper
public interface AddressMapper {

    @Mappings({
        @Mapping(source = "person.description", target = "description"),
        @Mapping(source = "hn", target = "houseNumber")
    })
    DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Integer hn);
}

Updating existing bean instances

In some cases you need mappings which don’t create a new instance of the target type but instead update an existing instance of that type. This sort of mapping can be realized by adding a parameter for the target object and marking this parameter with @MappingTarget.

@Mapper
public interface CarMapper {

  @Mappings({
          @Mapping(source = "manufacture", target = "make"),
          @Mapping(source = "noOfSeats", target = "seatCount")
      })
    void updateCarFromDto(CarDto carDto, @MappingTarget Car car);
}

Retrieving a mapper

The Mappers factory

Mapper instances can be retrieved via the org.mapstruct.factory.Mappers class. Just invoke the getMapper() method, passing the interface type of the mapper to return:

CarMapper mapper = Mappers.getMapper( CarMapper.class );
By convention, a mapper interface should define a member called `INSTANCE` which holds a single instance of the mapper type:
@Mapper
public interface CarMapper {

    CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );

    CarDto carToCarDto(Car car);
}

mappers generated by MapStruct are thread-safe and thus can safely be accessed from several threads at the same time.

Using dependency injection

A mapper using the CDI component model

@Mapper(componentModel = "cdi")
public interface CarMapper {

    CarDto carToCarDto(Car car);
}

Obtaining a mapper via dependency injection

@Inject
private CarMapper mapper;

Data type conversions

Implicit type conversions

MapStruct takes care of type conversions automatically in many cases. If for instance an attribute is of type int in the source bean but of type String in the target bean, the generated code will transparently perform a conversion by calling String#valueOf(int) and Integer#parseInt(String), respectively.

Currently the following conversions are applied automatically:

  • Between all Java primitive data types and their corresponding wrapper types, e.g. between int and Integer, boolean and Boolean etc. The generated code is null aware, i.e. when converting a wrapper type into the corresponding primitive type a null check will be performed.

  • Between all Java primitive number types and the wrapper types, e.g. between int and long or byte and Integer.

  • Between all Java primitive types (including their wrappers) and String, e.g. between int and String or Boolean and String. A format string as understood by java.text.DecimalFormat can be specified.

@Mapper
public interface CarMapper {

    @Mapping(source = "price", numberFormat = "$#.00")
    CarDto carToCarDto(Car car);

    @IterableMapping(numberFormat = "$#.00")
    List<String> prices(List<Integer> prices);
}
  • Between enum types and String.

  • Various Date, Time and Calendar related conversions.

Mapping object references

Typically an object has not only primitive attributes but also references other objects. In this case just define a mapping method for the referenced object type as well:

@Mapper
public interface CarMapper {

    CarDto carToCarDto(Car car);

    PersonDto personToPersonDto(Person person);
}

When generating the implementation of a mapping method, MapStruct will apply the following routine for each attribute pair in the source and target object:

  • If source and target attribute have the same type, the value will be simply copied from source to target. If the attribute is a collection (e.g. a List) a copy of the collection will be set into the target attribute.

  • If source and target attribute type differ, check whether there is another mapping method which has the type of the source attribute as parameter type and the type of the target attribute as return type. If such a method exists it will be invoked in the generated mapping implementation.

  • If no such method exists MapStruct will look whether a built-in conversion for the source and target type of the attribute exists. If this is the case, the generated mapping code will apply this conversion.

  • If no such method was found MapStruct will try to generate an automatic sub-mapping method that will do the mapping between the source and target attributes.

  • If MapStruct could not create a name based mapping method an error will be raised at build time, indicating the non-mappable attribute and its path.


Advanced mapping options

Default values and constants

Default values can be specified to set a predefined value to a target property if the corresponding source property is null. Constants can be specified to set such a predefined value in any case. Default values and constants are specified as String values and are subject to type conversion either via built-in conversions or the invocation of other mapping methods in order to match the type required by the target property.

A mapping with a constant must not include a reference to a source property.

http://mapstruct.org/documentation/stable/reference/html/#invoking-other-mappers