Map an object to an immutable object using the builder (using the indispensability annotation handler) in mapstruct

We use an immutable structure to create all DTOs. Now we want to map these objects to each other using mapstruct . But the generated DTOs are immutable and have no setters and no constructor that matches the builder pattern. They are filled only through the appropriate builder, accessed by the static builder() method.

Instead, we tried to map DTO1 to DTO2.Builder, which would work if mapstruct recognized the setter in Builder, but they did not have a return type, but returned Builder itself for free concatenation.

So here is an example code.

We have two interfaces

 @Value.Immutable public interface MammalDto { public Integer getNumberOfLegs(); public Long getNumberOfStomachs(); } 

and

 @Value.Immutable public interface MammalEntity { public Long getNumberOfLegs(); public Long getNumberOfStomachs(); } 

Then we have the Mapper interface for mapstruct:

 @Mapper(uses = ObjectFactory.class) public interface SourceTargetMapper { SourceTargetMapper MAPPER = Mappers.getMapper( SourceTargetMapper.class ); ImmutableMammalEntity.Builder toTarget(MammalDto source); } 

For mapstruct to find Builder, we need Factory:

 public class ObjectFactory { public ImmutableMammalDto.Builder createMammalDto() { return ImmutableMammalDto.builder(); } public ImmutableMammalEntity.Builder createMammalEntity() { return ImmutableMammalEntity.builder(); } } 

To generate the code, the compiler plugin was instructed to use both annotation handlers:

 <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.6.1</version> <configuration> <source>1.8</source> <target>1.8</target> <annotationProcessorPaths> <path> <groupId>org.immutables</groupId> <artifactId>value</artifactId> <version>2.2.8</version> </path> <path> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.2.0.Beta3</version> </path> </annotationProcessorPaths> </configuration> </plugin> 

Note. This will only work with mapstruct> 1.2.x. Older versions have a problem in the clean build ( mvn clean compile ) that they cannot find sources that are just built-in. In the second build (without clearing), they will find an immutables implementation because they were on the class path before running annotation handlers. Now this error is fixed.

It works like a charm. First, Invariable implementations of interfacts are generated, and mapstruct uses them to create a builder.

But the test shows that no properties are set:

 @Test public void test() { MammalDto s = ImmutableMammalDto.builder().numberOfLegs(4).numberOfStomachs(3l).build(); MammalEntity t = SourceTargetMapper.MAPPER.toTarget(s).build(); assertThat(t.getNumberOfLegs()).isEqualTo(4); assertThat(t.getNumberOfStomachs()).isEqualTo(3); } 

Statements fail. One look at the handler generated by mapstruct shows that it clearly did not find any setters:

 @Generated( value = "org.mapstruct.ap.MappingProcessor", //... ) public class SourceTargetMapperImpl implements SourceTargetMapper { private final ObjectFactory objectFactory = new ObjectFactory(); @Override public Builder toTarget(MammalDto source) { if ( source == null ) { return null; } Builder builder = objectFactory.createMammalEntity(); return builder; } } 

The empty constructor is returned. I think the reason is the setter implementation of the generated builder, because it returns itself to create a free API:

 public final Builder numberOfLegs(Long numberOfLegs) { this.numberOfLegs = Objects.requireNonNull(numberOfLegs, "numberOfLegs"); return this; } 

Is there any way that mapstruct can find these setters? Or even the best way to deal with such immutable objects with builders?

EDIT: As I said in the comment, I came across Issue # 782 . In version 1.2.0.Beta3, builders are still not supported. But there are several discussions on this topic, so it would be interesting to keep track of the problem if you have the same problem.

+8
java immutability mapping builder mapstruct
source share
3 answers

We had the same problem in our project. As a workaround, we used the Modifiable implementation of our immutable dto.

You can also try. Direct use of builders and object objects is better.

@Value.Modifiable generates an implementation with setters.

@Value.Style(create = "new") generates an open args constructor.

 @Value.Immutable @Value.Modifiable @Value.Style(create = "new") public interface MammalEntity { public Long getNumberOfLegs(); public Long getNumberOfStomachs(); } 

Then your cartographer will be simpler, there is no need for a factory object.

 @Mapper public interface SourceTargetMapper { ModifiableMammalEntity toTarget(MammalDto source); } 

In this case, MapStruct can see setters in ModifiableMammalEntity

Using such a cartographer will look like

 // Here you don't need to worry about implementation of MammalEntity is. The interface `MammalEntity` is immutable. MammalEntity mammalEntity = sourceTargetMapper.toTarget(source); 
+2
source share

You can configure Immutables to create setters in the builder:

 @Value.Immutable @Value.Style(init = "set*") public interface MammalEntity { public Long getNumberOfLegs(); public Long getNumberOfStomachs(); } 

And you do not need ObjectBuilder, you can directly use the generated Immutable class

 @Mapper(uses = ImmutableMammalEntity.class) public interface SourceTargetMapper { SourceTargetMapper MAPPER = Mappers.getMapper( SourceTargetMapper.class ); ImmutableMammalEntity.Builder toTarget(MammalDto source); } 

You can even define these settings in your annotation.

 @Value.Style(init = "set*") public @interface SharedData {} 

and use instead

 @SharedData @Value.Immutable public interface MammalEntity { public Long getNumberOfLegs(); public Long getNumberOfStomachs(); } 
0
source share

Starting with version 1.3, MapStruct supports persistent. See here for more details.

0
source share

All Articles