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! 🧑💻