Skip to content

guide beanmapping quarkus

devonfw-core edited this page Nov 1, 2021 · 6 revisions

Bean mapping with Quarkus

This guide will show bean-mapping in particular for a Quarkus application. We recommend using MapStruct with a Quarkus application because the other bean-mapper frameworks are using Java reflections. They are not supported in GraalVm right now and causes problems building native applications. MapStruct is a code generator that greatly simplifies the implementation of mappings between Java bean types based on a convention over configuration approach. The mapping code will be generated at compile-time and uses plain method invocations and thus is fast, type-safe, and easy to understand. MapStruct has to be configured to not use Java reflections but it will be shown in this guide.

You can find the official MapStruct reference guide and a general introduction to MapStruct from Baeldung.

MapStruct Dependency

To get access to MapStruct we have to add the dependency to our POM.xml:

<dependency>
  <groupId>org.mapstruct</groupId>
  <artifactId>mapstruct</artifactId>
  <version>1.4.2.Final</version>
  <scope>provided</scope>
</dependency>

MapStruct provides an annotation processor that also has to be added to the POM.xml

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-compiler-plugin</artifactId>
	<version>3.8.1</version>
	<configuration>
		<source>1.8</source>
		<target>1.8</target>
		<annotationProcessorPaths>
			<path>
				<groupId>org.mapstruct</groupId>
				<artifactId>mapstruct-processor</artifactId>
				<version>1.4.2.Final</version>
			</path>
		</path>
		</annotationProcessorPaths>
	</configuration>
</plugin>

MapStruct takes advantage of generated getters, setters, and constructors from the Lombok library, follow this Lombok with Mapstruct guide to get Lombok with Mapstruct working.

MapStruct Configuration

We already discussed the benefits of dependency injection and MapStruct supports CDI with EJB, spring, and jsr330. The default retrieving method for a mapper is a factory that uses reflections and should be avoided. The component model should be set to CDI, as this will allow us to easily inject the generated mapper implementation. The component model can be configured in multiple ways.

Simple Configuration

Add the attribute componentModel to the @Mapper annotation in the mapper interface.

@Mapper(compnentModel = "cdi")
public interface ProductMapper{
  ...
}

MapperConfig Configuration

Create a shared configuration that can be used for multiple mappers. Implement an Interface and use the annotation @MapperConfig for the class. You can define all configurations in this interface and pass the generated MapperConfig.class with the config attribute to the mapper. The MapperConfig also defines the InjectionStrategy and MappingInheritaceStrategy both will be explained later. A list of all configurations can be found here.

@MapperConfig(
  compnentModel = "cdi",
  mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_FROM_CONFIG
  injectionStrategy =InjectionStrategy.CONSTRUCTOR
)
public interface MapperConfig{
}
@Mapper( config = MapperConfig.class )
public interface ProductMapper{
  ...
}

Any attributes not given via @Mapper will be inherited from the shared configuration MapperConfig.class.

Configuration via annotation processor options

The MapStruct code generator can be configured using annotation processor options. You can pass the options to the compiler while invoking javac directly, or add the parameters to the maven configuration in the POM.xml

We are also using the constructor injection strategie to avoid field injections and potential reflections also it will simplify our tests. The option to pass the parameter to the annotation processor in the POM.xml is used and can be inspected in our quarkus reference project.

A list of all annotation processor options can be found here.

Basic Bean-Mapper Usage

To use the mapper we have to implement the mapper interface and the function prototypes with a @Mapper annotation.

@Mapper
public interface ProductMapper {

  ProductDto map(ProductEntity model);

  ProductEntity create(NewProductDto dto);
}

The MapStruct annotation processor will generate the implementation for us under /target/generated-sources/, we just need to tell it that we would like to have a method that accepts an ProductEntity entity and returns a new ProductDto DTO.

The generated mapper implementation will be marked with the @ApplicationScoped annotation and thus can be injected into fields, constructor arguments, etc. using the @Inject annotation:

public class ProductRestService{

  @Inject
  ProductMapper mapper;
}

That is the basic usage of a Mapstruct mapper. In the next chapter, we go a bit into detail and show some more configurations.

Advanced Bean-Mapper Usage

Let´s assume our Product entity and the ProductDto has some different named property that should be mapped. Add a mapping annotation to map the property type from Product to kind from ProductDto. We define the source name of the property and the target name.

@Mapper
public interface ProductMapper {
  @Mapping(target = "kind", source = "type")
  ProductDto map(ProductEntity entity);

  @InheritInverseConfiguration(name = "map" )
  ProductEntity create(ProductDto dto);
}

For bi-directional mappings, we can indicate that a method shall inherit the inverse configuration of the corresponding method with the @InheritInverseConfiguration. You can omit the name parameter if the result type of method A is the same as the single-source type of method B and if the single-source type of A is the same as the result type of B. If multiple applies the attribute name is needed. Specific mappings from the inversed method can (optionally) be overridden, ignored, and set to constants or expressions.

The mappingInheritanceStrategy can be defined as showed in MapStruct Configuration the existing options can be found here.

Not always a mapped attribute has the same type in the source and target objects. For instance, an attribute may be of type int in the source bean but of type Long in the target bean.

Another example are references to other objects which should be mapped to the corresponding types in the target model. E.g. the class ShoppingCart might have a property content of the type Product which needs to be converted into an ProductDto object when mapping a ShoppingCart object to ShoppingCartDto. For these cases, it’s useful to understand how Mapstruct is converting the data types and the object references.

Also, the Chapter for nested bean mappings will help to configure MapStruct to map arbitrary deep object graphs.

You can study running MapStruct implementation examples given by MapStruct or in our Quarkus reference project

Clone this wiki locally