Skip to content

guide beanmapping quarkus

devonfw-core edited this page Dec 13, 2022 · 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 use Java reflections. They are not supported in GraalVm right now and cause problems when 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 is thus fast, type-safe, and easy to understand. MapStruct has to be configured to not use Java reflections, which 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. MapStruct supports CDI with EJB, spring, and jsr330. The default retrieving method for a mapper is a factory that uses reflections, which 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 of which 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 also use the constructor injection strategy to avoid field injections and potential reflections. This will also 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 a ProductEntity entity and returns a new ProductDto DTO.

The generated mapper implementation will be marked with the @ApplicationScoped annotation and can thus 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’ll go into a bit more detail and show some more configurations.

Advanced Bean-Mapper Usage

Let´s assume that our Product entity and the ProductDto have some differently named properties 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 apply, the attribute name is needed. Specific mappings from the inverse method can (optionally) be overridden, ignored, or set to constants or expressions.

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

A mapped attribute does not always have 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 a ProductDto object when mapping a ShoppingCart object to ShoppingCartDto. For these cases, it’s useful to understand how Mapstruct converts the data types and the object references.

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

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

Clone this wiki locally