MapStruct Tutorial - Nested Calls Between Mappers 🚀

Time: Column:Java views:205

Learn how to use MapStruct to simplify complex Java Bean mappings by implementing nested mappers. This step-by-step guide covers defining source and target classes, creating mappers, and leveraging uses for elegant mapping solutions.

Defining Article and Person Classes

In this tutorial, we’ll explore how to construct nested mappers with MapStruct, an efficiency tool for Java Bean mapping that minimizes boilerplate code. All you need to do is define an interface, and MapStruct generates the mapping logic automatically. Previously, we focused on mapping individual objects. Now, let’s dive into more complex mappings! 🧙‍♂️✨


1. Preparing the Objects 🎯

We’ll define two classes: Article and Person. The Person class contains two simple fields, while the Article class contains two simple fields as well and references a Person field. We’ll also define corresponding DTO classes.

To make things interesting, the field names in the Person class and its DTO are intentionally different! 😈

Source Classes

@Getter
@Setter
public class Article {
    private int id;
    private String name;
    private Person author;
}

@Getter
@Setter
public class Person {
    private String id;
    private String name;
}

Target Classes

@Getter
@Setter
public class ArticleDTO {
    private int id;
    private String name;
    private PersonDTO author;
}

@Getter
@Setter
public class PersonDTO {
    private String personId;
    private String personName;
}

Now that the base classes are ready, let’s move on to defining the mappers! 🛠️


2. Defining Nested Mappers as Methods 🔗

Let’s start with a simple mapper for the Article class:

@Mapper
public interface ArticleMapper {

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

    ArticleDTO articleToArticleDto(Article article);
}

Generated Mapping Code

public class ArticleMapperImpl implements ArticleMapper {

    @Override
    public ArticleDTO articleToArticleDto(Article article) {
        if ( article == null ) {
            return null;
        }

        ArticleDTO articleDTO = new ArticleDTO();

        articleDTO.setId( article.getId() );
        articleDTO.setName( article.getName() );
        articleDTO.setAuthor( personToPersonDTO( article.getAuthor() ) );

        return articleDTO;
    }

    protected PersonDTO personToPersonDTO(Person person) {
        if ( person == null ) {
            return null;
        }

        PersonDTO personDTO = new PersonDTO();

        return personDTO;
    }
}

Here, MapStruct automatically generates the conversion for Person to PersonDTO. However, because the field names don’t match, it cannot map them automatically. 😓 Let’s fix that!


3. Defining the PersonMapper 🎨

We’ll define a PersonMapper interface and specify the mapping relationship explicitly using the @Mapping annotation:

@Mapper
public interface PersonMapper {
    PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class);

    @Mapping(source = "id", target = "personId")
    @Mapping(source = "name", target = "personName")
    PersonDTO personToPersonDTO(Person person);
}

4. Using the PersonMapper in ArticleMapper

(A) Manual Mapping Method

We can use the Mappers.getMapper() method to call the PersonMapper manually within ArticleMapper:

default PersonDTO personToPersonDto(Person person) {
    return Mappers.getMapper(PersonMapper.class).personToPersonDTO(person);
}

Generated Code

@Override
public ArticleDTO articleToArticleDto(Article article) {
    if ( article == null ) {
        return null;
    }

    ArticleDTO articleDTO = new ArticleDTO();

    articleDTO.setId( article.getId() );
    articleDTO.setName( article.getName() );
    articleDTO.setAuthor( personToPersonDto( article.getAuthor() ) );

    return articleDTO;
}

Although this works, it feels a bit clunky. Let’s see how to make it more elegant. 😌


(B) Elegant (Automated) Mapping

We can use the uses parameter in the @Mapper annotation to directly point to the PersonMapper. This way, no additional methods are required:

@Mapper(uses = PersonMapper.class)
public interface ArticleUsingPersonMapper {

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

    ArticleDTO articleToArticleDto(Article article);
}

Generated Code

public class ArticleUsingPersonMapperImpl implements ArticleUsingPersonMapper {

    private final PersonMapper personMapper = PersonMapper.INSTANCE;

    @Override
    public ArticleDTO articleToArticleDto(Article article) {
        if ( article == null ) {
            return null;
        }

        ArticleDTO articleDTO = new ArticleDTO();

        articleDTO.setId( article.getId() );
        articleDTO.setName( article.getName() );
        articleDTO.setAuthor( personMapper.personToPersonDTO( article.getAuthor() ) );

        return articleDTO;
    }
}

Notice how the author field is automatically mapped using the PersonMapper without us specifying the method explicitly. Isn’t that smooth and elegant? 🌟


Conclusion

With MapStruct, you can create clean and maintainable mappings for complex object structures by utilizing nested mappers. Whether you opt for a manual method or a more automated approach, the tool provides flexibility and convenience.

Happy coding! 🧑‍💻