Object Mapping - MapStruct - Java

 

MapStruct is a code generator that simplifies the conversion of one object to another. It uses annotations and mapping methods to automatically generate the implementations of conversion classes. MapStruct is particularly useful when converting between DTOs and DAOs.

1. Dependency Declaration

Using the MapStruct library requires configuration in the dependency management (here Maven).

First, add the following dependency to the pom.xml file:

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.5.3.Final</version>
</dependency>

 

1.1 Adding the Plugin: Generic Case

Add the following plugin to the pom.xml file:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.11.0</version>
            <configuration>
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>1.5.3.Final</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

 

1.2 Adding the Plugin: Lombok Usage Case

If Lombok is used in the project, then it is necessary to use the following plugin in the pom.xml file:

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

 

2. Source Generation

Source generation (implementation) in the rest of the article is done using the following Maven command:

mvn clean install

 

3. Simple Mapping

First, let's define the following two classes:

public class UserDTO {
    private long id;
    private String usename;

    // Getter / Setter
}

public class UserDBO {
    private long id;
    private String usename;

    // Getter / Setter
}

Note: The getter/setter can be generated using Lombok via the @Getter and @Setter annotations on both classes.

 

The mapping interface definition is as follows:

@Mapper
public interface UserMapper {
    UserDTO mapToUserDTO(UserDBO user);
}

Here, we want to map the UserDBO class to UserDTO. The @Mapper annotation tells MapStruct to generate an implementation of this mapper interface.

 

The mapping generation is done via the following Maven command (to be used before running the application to avoid any errors):

mvn clean install

 

In the target/generated-sources/mapstruct folder, the mapper implementations are generated. Here, we find the implementation of UserMapper:

import javax.annotation.processing.Generated;

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2023-01-18T21:56:41+0100",
    comments = "version: 1.5.3.Final, compiler: javac, environment: Java 17.0.3.1 (Oracle Corporation)"
)
public class UserMapperImpl implements UserMapper {

    @Override
    public UserDTO mapToUserDTO(UserDBO user) {
        if ( user == null ) {
            return null;
        }

        UserDTO userDTO = new UserDTO();

        userDTO.setId( user.getId() );
        userDTO.setUsename( user.getUsename() );

        return userDTO;
    }
}

 

To instantiate the UserMapper, use the Mappers.getMapper(TheMapperInterface.class) command:

UserMapper mapper = Mappers.getMapper(UserMapper.class);

 

4. Dependency Injection with Spring

It is possible to instantiate the UserMapper using Spring dependency injection. To do this, use the "componentModel" attribute of the @Mapper annotation as follows:

@Mapper(componentModel = "spring")
public interface UserMapper {
    UserDTO mapToUserDTO(UserDBO user);
}

 

5. Mapping Fields with Different Names

Mapping classes with different names is done as follows:

Definition of the models to be mapped:

public class UserDTO {
    private long id;
    private String name;
    private String surname;

    // Getter / Setter
}

public class UserDBO {
    private long id;
    private String firstName;
    private String lastName;

    // Getter / Setter
}

 

Definition of the mapper:

@Mapper
public interface UserMapper {
    @Mapping(target = "name", source = "firstName")
    @Mapping(target = "surname", source = "lastName")
    UserDTO mapToUserDTO(UserDBO user);
}

Here, the UserMapper.mapToUserDTO method identifies the mappings between UserDBO and UserDTO as follows:

  • UserDBO.firstName to UserDTO.name,
  • UserDBO.lastName to UserDTO.surname.

 

6. Mapping Child Objects

If the UserDBO class contained non-generic classes (e.g., DBO classes), it would be necessary to create a second method in our mapping interface to convert it as well. Example:

@Mapper
public interface UserMapper {
    @Mapping(target = "name", source = "firstName")
    @Mapping(target = "surname", source = "lastName")
    UserDTO mapToUserDTO(UserDBO user);

    @Mapping(target = "id", source = "identifier")
    RightDTO mapToUserDTO(RightDBO right);
}

 

7. Mapping with Type Conversion

Mapping classes containing different types is done as follows:

Definition of the models to be mapped:

public class UserDTO {
    private long id;
    private String name;
    private String surname;
    private String birthdate;

    // Getter / Setter
}

public class UserDBO {
    private long id;
    private String firstName;
    private String lastName;
    private Date birthdate;

    // Getter / Setter
}

 

Definition of the mapper:

@Mapper
public interface UserMapper {
    @Mapping(target = "name", source = "firstName")
    @Mapping(target = "surname", source = "lastName")
    UserDTO mapToUserDTO(UserDBO user);

    default String setBirthdate(Date birthdate) {
        return birthdate.toString();
    }
}

Here, the UserMapper.setBirthdate method identifies the mappings between the Date type to String. However, this will map all dates to strings following this method.

 

If multiple fields of the same type need to follow different mapping procedures, then it is necessary to use the qualifiedByName attribute of the @Mapping annotation as follows:

@Mapper
public interface UserMapper {

    @Mapping(target = "name", source = "firstName")
    @Mapping(target = "surname", source = "lastName")
    @Mapping(target = "birthdate", source = "birthdate", qualifiedByName = "birthdateToString")
    UserDTO mapToUserDTO(UserDBO User);

    @Named("birthdateToString") 
    public static String birthdateToString(Date birthdate) {
        return birthdate.toString();
    }
}

When converting the birthdate field from Date to String, MapStruct will use the UserMapper.birthdateToString(Date) method to perform the conversion. Note that this new method is static.

 

It is also possible to use the dateFormat attribute of the @Mapper annotation to more easily convert a date. Example:

@Mapper
public interface UserMapper {

    @Mapping(target = "name", source = "firstName")
    @Mapping(target = "surname", source = "lastName")
    @Mapping(target = "birthdate", source = "birthdate", dateFormat = "yyyy/MM/dd HH:mm:ss")
    UserDTO mapToUserDTO(UserDBO user);
}

 

8. Mapping with Data Modification

Mapping containing data modifications can be done following one of the first two examples from the previous point.

 

9. Conclusion

In conclusion, MapStruct is a useful tool to simplify object conversion. It saves time and makes the code more readable and easier to maintain through the automatic generation of conversion code between different objects. However, it is important to know its limitations to anticipate mandatory manual mappings.

 

 

LauLem.com - (Pages in French) Conditions of Use - Legal Information - Cookie Policy - Personal Data Protection Policy - About