Java Bean Mapping — Usage of Mapping Libraries, Part II

Güven Seçkin
iyzico.engineering
Published in
8 min readAug 4, 2020

--

In the first part of this article, I introduced the idea of mapping objects and how we can map them. Instead of coding each line of mapping process by ourselves, we may prefer using a library to automate it.

Let’s find a new library for mapping our objects.

Model Mapper

Our first library is going to be Model Mapper. Let’s dive into the idea behind this library.

Model Mapper consists of two processes Matching Process and Mapping Process. The matching process is understanding which field will be mapped to the other one. The mapping process is mapping source to destination.

First, we should add Model Mapper to our project as a bean.

When we inject Model Mapper to our service, spring will give us this bean.

Let’s map our object using Model Mapper. I will write some tests to be an example.

As seen above, new fields have been added to the User entity and User Request Object in order to give different examples.

There is a map function that belongs to the Model Mapper. The first argument of the function is source object and the last parameter is destination class information. With these parameters, Model Mapper is able to understand which object will be converted to the other one.

There is also a nice feature. When you check the token field, you will realize that this type of field is different in both objects. The token is boxed long type in User Request whereas that is String type in User entity. The Model Mapper can also be used to overcome this kind of conversions.

How does model mapper achieve this conversion? 🤔

There is a term called NameTransformers which is used for extraction field name from getter and setter functions. Model Mapper omits “get” keyword from the source object and “set” keyword from the destination object. In our case, the username is extracted from its getter and setter in order to obtain a pure field name. After extracting, NameTokenizers are used to tokenize properties for matching.

We can customize our mappings with some configurations which are represented by the Model Mapper. To do this, we have TypeMap.

TypeMap is an expression map that helps us to customize our mappings. Let’s see the usage of TypeMap.

TypeMap should be added to Model Mapper in the configuration part. Therefore, when we inject the Model Mapper, this mapping can be used.

At first, we need to create TypeMap with source and destination classes.

Then we can create a map using getter-setter methods. If you confuse double columns, you may want to consider checking the instance method reference in java. With this approach, we can extend Model Mapper mapping skills. In this case, It was explicitly declared that City should be mapped to the Address field.

Now, we have learned how can we create a mapping table. However, what if we want to add more functionality during the mapping process rather than just map them?

We may need to save our company field with a “Company” keyword. To do this, there should be a converter that adds Company keyword to the end of the company.

In addition to TypeMap, the Model Mapper also has its own Converter. Thanks to this Converter, we are able to add more functionality during the mapping process. In this case, It clearly indicates that, while mapping companies, the converter should be used instead of default one. You may also prefer skipping some fields for mapping or proving destination values from yourself. For more detail, please check the article.

Property Mapping

Performance Of Mapping Libraries

As a programmer, we are not only responsible for clean and understandable code but also for effective and high-performance code. In an interview, I came across this kind of question. After a short search, I found an article about the performance of mapping frameworks. At first glance, it was obvious that Model Mapper has a problem in terms of performance.

Less is better

After this disappointment, I had to find a new library which does not only helps to map but also be high performing. At the top of the table, there is one more mapping library which is MapStruct.

What differences between Model Mapper and MapStruct make such a noticeable performance difference? 🤔

Model mapper uses reflection for mapping. This process is expensive and impacts our performance. In contrast, MapStruct uses a compile-time code generation approach. With this approach, we let the library generate the code for us. When we build our codes, MapStruct will generate codes according to our definitions. Let’s dive into a new library!

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:

--

--