We are using the immutables framework to generate all DTOs. Now we would like to map these objects one to another with mapstruct. But the generated DTOs are immutable and have no setters and no constructor, corresponding to the builder pattern. They are only filled through the corresponding builder accessed by a static builder()
-method.
We instead tried to map DTO1 to DTO2.Builder which would work if mapstruct would recognize the setter in the Builder but these do not have void return type but return the Builder itself for fluent concatenation.
So here is the code of the example.
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 the Builder we need a Factory:
public class ObjectFactory {
public ImmutableMammalDto.Builder createMammalDto() {
return ImmutableMammalDto.builder();
}
public ImmutableMammalEntity.Builder createMammalEntity() {
return ImmutableMammalEntity.builder();
}
}
In order to generate the code the compiler plugin was instructed to use both annotation processors:
<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 work only with mapstruct version > 1.2.x. Older versions have the problem in a clean build (mvn clean compile
) that they do not find the sources that immutables just built. In a second build (without clean) they would find the immutables implementations because they were on the classpath before annotation processors were run. This bug is fixed now.
This works like a charm. First the Immutable implementations of the interfactes are generated and mapstruct uses them to generate the 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);
}
The asserts fail. One look at the mapper generated by mapstruct shows that it has obviously not found 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 builder is returned. I think the reason is the setter implementation of the generated builder because it returns itself to create a fluent API:
public final Builder numberOfLegs(Long numberOfLegs) {
this.numberOfLegs = Objects.requireNonNull(numberOfLegs, "numberOfLegs");
return this;
}
Is there a way to let mapstruct find these setters? Or even a better way to deal with such immutable objects with builders?
EDIT: As I stated in the comment I ran into Issue #782. In version 1.2.0.Beta3 builders are still not supported. But there are several discussions on this topic so it might be interesting to follow the issue if one has the same problem.
In general, mapping collections with MapStruct works the same way as for simple types. Basically, we have to create a simple interface or abstract class, and declare the mapping methods. Based on our declarations, MapStruct will generate the mapping code automatically.
Java Prime PackMapStruct allows to use Builders. We can use Builder frameworks or can use our custom builder. In below example, we are using a custom builder.
Enclosing class: MappingConstants public static final class MappingConstants.ComponentModel extends Object. Specifies the component model constants to which the generated mapper should adhere. It can be used with the annotation Mapper.componentModel() or MapperConfig.componentModel()
MapStruct is a code generator that greatly simplifies the implementation of mappings between Java bean types based on a convention over configuration approach. The generated mapping code uses plain method invocations and thus is fast, type-safe and easy to understand.
You can configure Immutables to generate setters in the builder:
@Value.Immutable
@Value.Style(init = "set*")
public interface MammalEntity {
public Long getNumberOfLegs();
public Long getNumberOfStomachs();
}
And you don't need the 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 own annotation
@Value.Style(init = "set*")
public @interface SharedData {}
and use that instead
@SharedData
@Value.Immutable
public interface MammalEntity {
public Long getNumberOfLegs();
public Long getNumberOfStomachs();
}
Since 1.3 MapStruct supports Immutables. Look here for more details.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With