By this post I'll be starting a series of posts dedicated to different code generators, which makes our lives easier, and more fun. As we don't need to write boring code!
Did you ever find yourself in a situation where you needed to copy data from one Java bean to another? Or maybe from an Entity to DTO? Does next code look familiar to you ?
MapStruct to the rescue! MapStruct is an Annotation Processor based code generator. It allows you to specify mappings between Java bean types through declaring interface of such mapping. And the code generation part will provide an implementation.
For demo purposes of this post let's start with a simple example. Assume we have an @Embeddable AddressEntity class, as part of our domain model. And we'd like to create Address POJOs from this embeddable.
We could have done it with the help of a constructor:
Or maybe with a static method:
And it looks OK, and seems easy enough to write. But what if we do have more properties? A lot more properties... Or maybe we are just that lazy :). So if we would do it with MapStruct it'll look like this:
Looks easy, right?
Let's look closer at what should be done to create a mapper. First we need to create an interface (called AddressMapper in our case) and put @Mapper annotation on it. This annotation is a marker annotation for MapStruct. It'll find all mappers and generate the implementation for them. If you'd like you can make mappers from abstract classes as well, but personally I prefer interfaces.
As we would like to access our mapper somehow - INSTANCE interface member is defined. To get the generated mapper we need to call Mappers.getMapper( ... ).
Now how this would work on practice and when this code generation will happen? As it's an Annotation Processor based code generator we should include it in our build process. In case of Maven project it can be done by specifying annotation processor in Maven's compiler plugin like this:
So that was easy. But what if we have a more complex data types?
Let's consider UserEntity and User pair and create a mapper for them:
In this case, we see that we don't have one to one match between properties of both types. Some have different names (UserEntity.lastVisit and User.visit), some are inside of value objects (UserEntity.userName and UserEntity.password inside User.credentials), enum is represented as a string, and even some are missing (like id). We also have properties from previously considered addresses pair as well.
Here's a possible way to define a mapper for such type pair:
First notice that @Mapper annotation has additional uses parameter defined. It is needed as we would like our mapper to use the mappings that we defined in AddressMapper for address types. Next let's move to mapping method UserMapper#toUser. There's a few @Mapping annotations declared. It's easy to see that they allow us to define a source property and where it is located in the target type. So this will help us to handle naming mismatch, or cases where properties are inside of other beans. The only thing left to notice here is a default method UserMapper#genderToString. As we see it converts an enum - to a string. This method will be picked up by MapStruct and used in mapping enum to string. It also shows a way on how a complex mapping can be done by hand, if needed. You can provide you own mapping functions with complex logic which later can be used by MapStruct.
And here's a test to make sure that everything works as expected:
MapStruct is interesting not only because it saves time by generating mapping code, but by the concept of mapper by itself. Even if you end up with writing parts of mapping code on your own, a mapper will give you a separation of concerns and you wouldn't need any constructors or factory methods in your bean types.
All the examples with tests and configurations can be found in this GitHub repository.
To be continued...
COMMENTS