Java Bean Mapping — Usage of Mapping Libraries, Part II
By: Güven Seçkin
Tags: Java, Mapping

MapStruct

As mentioned, MapStruct generates mapping codes at the compile time. Therefore, it is the same code that is written by us as performance.

Well, How MapStruct generate these codes at the compile time? 🤔

Annotation processing!

Annotation processing is integrated into the java compiler. Passing processor flag to javac, we are able to invoke annotation processors. The processor is only able to generate new source files rather than modifying existing sources. If you are interested in the topic, I suggest checking this book.

“Core Java Volume 2, Advanced Features at Chapter 8.6.3 Using Annotation to Generate Source Code”.

MapStruct uses this approach in order to generate our mapping codes. Now let’s see some code.

I would like to recap our Request DTO and the Entity object.

These are our Mapping objects. Basically, in order to define our mapping logic, we should define an interface. MapStruct will implement this interface and generate our concrete methods.

We define our mappings with abstract methods. As could be seen, we should also annotate our interface as “Mapper” and component model also should be added for dependency injection.

After the compilation, there will be some concrete classes under target/generated-sources/annotations/…

This code is generated by MapStruct according to our definitions. The library can understand the source and destination from the parameter and the return values. Besides, we are able to also implement list mapping easily. List conversions will use one to one mapping into a for-loop.

It also adds a component annotation for spring because we explicitly declared that this component belongs to spring.

componentModel = “spring”

Now, let’s add more functionality to our mapper.

As could be seen in the object, I added a new field called Status. The purpose of this field is to keep the user status. When the user register, we should initialize the user with active status. You may want to achieve this during the mapping process.

After mapping all parts, we also add some definitions. To do this we have an annotation offered by MapStruct.

As of Java 8, we are able to add concrete methods to our interfaces. To do this, we use static or default methods.

With the AfterMapping annotation, this function will be called at the end of the mapping process from the concrete class. We should also add MappingTarget annotation to indicate which object is targeted. Therefore, after the mapping, we can initialize the Status field with the default value.

Let’s look at another example. Suppose that we should save our companies with the Company keyword to the database.

How can we achieve this kind of mapping in MapStruct? 🤔

To do this, we should add Mappings annotation which could be consist of many mappings. In the mapping annotation, we should add source and the target value. Then, we can define our own intent during the mapping process. In this case, we are able to use Named annotation in order to give a specific name to our default function. After that, we are able to indicate our logic to the mapping. In the concrete class, the function will be called and set the return value of the function to the Company field as below.

user.setCompany( mapCompany( userRequest ) );

With this approach, you are able to define your own mappings rule for specific fields.

We can also define some mappings for specific types. I mean, for all Long to String conversions, we may need a specific mapping for them.

To do this, we have to add uses parameter to the Mapper annotation.

As could be seen above, Token Mapper was used for mappings. You are able to add more classes inside of curly brackets.

In this case, all Long to String field conversions, this concrete method will be used. MapStruct is able to understand this conversion from the parameter type and the return value type of the method.

It is time to map the User entity to the User Response object. Let’s see different examples!

Suppose that, we have to add another parameter to our mapper function because we would like to set this parameter to the destination field.

User Response Object

As you may have noticed, AccountType has been added to the response object. How could we pass AccountType as a parameter to our mapper function like below?

To achieve this, we have to learn one more Annotation. Context!

We can add more parameters to our mapper function with Context annotation. With AfterMapping, AccountType was able to set as in the example! Thanks to Context annotation, we are able to bind parameters to each other. Instead of setting statically, this approach is recommended.

Wait!

This is an abstract class! Could we use abstract classes instead of interfaces?

The answer is yes. As of Java 8, we are able to use interfaces instead of using abstract classes because of default and static methods. There may be one reason for using abstract classes rather than using interfaces. If we have to inject other services to our mapper, we should use abstract classes.

During the mapping process, we may need to use other services for retrieving data. In our case, there is a country service which consists of one function. With this function, we are able to retrieve the country name from the country id.

For now, adding County keyword to the end of country ID will be enough.

How could we inject this service to our mapper?

There is one more parameter that belongs to Mapping annotation. We are able to write our own expressions manually in this field. For country name mapping, this expression will be used.

For two reasons, I did not find this approach sufficient. First off, Autowire was used for dependency injection which is field injection. The other reason is expression seems too manual. We have to write all expression as a String. It is prone to making mistakes.

Fortunately, If there is a compile-time error in our mapping definitions, we will produce an error.

You may also consider checking more usage of these mapping libraries. Links have been added to the references.

For all codes and configurations, the GitHub URL has been attached.

References: