mirror of
https://github.com/mapstruct/mapstruct.git
synced 2025-07-12 00:00:08 +08:00
866 lines
30 KiB
Plaintext
866 lines
30 KiB
Plaintext
[[defining-mapper]]
|
|
== Defining a mapper
|
|
|
|
In this section you'll learn how to define a bean mapper with MapStruct and which options you have to do so.
|
|
|
|
[[basic-mappings]]
|
|
=== Basic mappings
|
|
|
|
To create a mapper simply define a Java interface with the required mapping method(s) and annotate it with the `org.mapstruct.Mapper` annotation:
|
|
|
|
.Java interface to define a mapper
|
|
====
|
|
[source, java, linenums]
|
|
[subs="verbatim,attributes"]
|
|
----
|
|
@Mapper
|
|
public interface CarMapper {
|
|
|
|
@Mapping(target = "manufacturer", source = "make")
|
|
@Mapping(target = "seatCount", source = "numberOfSeats")
|
|
CarDto carToCarDto(Car car);
|
|
|
|
@Mapping(target = "fullName", source = "name")
|
|
PersonDto personToPersonDto(Person person);
|
|
}
|
|
----
|
|
====
|
|
|
|
The `@Mapper` annotation causes the MapStruct code generator to create an implementation of the `CarMapper` interface during build-time.
|
|
|
|
In the generated method implementations all readable properties from the source type (e.g. `Car`) will be copied into the corresponding property in the target type (e.g. `CarDto`):
|
|
|
|
* When a property has the same name as its target entity counterpart, it will be mapped implicitly.
|
|
* When a property has a different name in the target entity, its name can be specified via the `@Mapping` annotation.
|
|
|
|
[TIP]
|
|
====
|
|
The property name as defined in the http://www.oracle.com/technetwork/java/javase/documentation/spec-136004.html[JavaBeans specification] must be specified in the `@Mapping` annotation, e.g. _seatCount_ for a property with the accessor methods `getSeatCount()` and `setSeatCount()`.
|
|
====
|
|
[TIP]
|
|
====
|
|
By means of the `@BeanMapping(ignoreByDefault = true)` the default behavior will be *explicit mapping*, meaning that all mappings (including nested ones) have to be specified by means of the `@Mapping` and no warnings will be issued on missing target properties.
|
|
This allows to ignore all fields, except the ones that are explicitly defined through `@Mapping`.
|
|
====
|
|
[TIP]
|
|
====
|
|
Fluent setters are also supported.
|
|
Fluent setters are setters that return the same type as the type being modified.
|
|
|
|
E.g.
|
|
|
|
```
|
|
public Builder seatCount(int seatCount) {
|
|
this.seatCount = seatCount;
|
|
return this;
|
|
}
|
|
```
|
|
====
|
|
|
|
|
|
To get a better understanding of what MapStruct does have a look at the following implementation of the `carToCarDto()` method as generated by MapStruct:
|
|
|
|
.Code generated by MapStruct
|
|
====
|
|
[source, java, linenums]
|
|
[subs="verbatim,attributes"]
|
|
----
|
|
// GENERATED CODE
|
|
public class CarMapperImpl implements CarMapper {
|
|
|
|
@Override
|
|
public CarDto carToCarDto(Car car) {
|
|
if ( car == null ) {
|
|
return null;
|
|
}
|
|
|
|
CarDto carDto = new CarDto();
|
|
|
|
if ( car.getFeatures() != null ) {
|
|
carDto.setFeatures( new ArrayList<String>( car.getFeatures() ) );
|
|
}
|
|
carDto.setManufacturer( car.getMake() );
|
|
carDto.setSeatCount( car.getNumberOfSeats() );
|
|
carDto.setDriver( personToPersonDto( car.getDriver() ) );
|
|
carDto.setPrice( String.valueOf( car.getPrice() ) );
|
|
if ( car.getCategory() != null ) {
|
|
carDto.setCategory( car.getCategory().toString() );
|
|
}
|
|
carDto.setEngine( engineToEngineDto( car.getEngine() ) );
|
|
|
|
return carDto;
|
|
}
|
|
|
|
@Override
|
|
public PersonDto personToPersonDto(Person person) {
|
|
//...
|
|
}
|
|
|
|
private EngineDto engineToEngineDto(Engine engine) {
|
|
if ( engine == null ) {
|
|
return null;
|
|
}
|
|
|
|
EngineDto engineDto = new EngineDto();
|
|
|
|
engineDto.setHorsePower(engine.getHorsePower());
|
|
engineDto.setFuel(engine.getFuel());
|
|
|
|
return engineDto;
|
|
}
|
|
}
|
|
----
|
|
====
|
|
|
|
The general philosophy of MapStruct is to generate code which looks as much as possible as if you had written it yourself from hand. In particular this means that the values are copied from source to target by plain getter/setter invocations instead of reflection or similar.
|
|
|
|
As the example shows the generated code takes into account any name mappings specified via `@Mapping`.
|
|
If the type of a mapped attribute is different in source and target entity,
|
|
MapStruct will either apply an automatic conversion (as e.g. for the _price_ property, see also <<implicit-type-conversions>>)
|
|
or optionally invoke / create another mapping method (as e.g. for the _driver_ / _engine_ property, see also <<mapping-object-references>>).
|
|
MapStruct will only create a new mapping method if and only if the source and target property are properties of a Bean and they themselves are Beans or simple properties.
|
|
i.e. they are not `Collection` or `Map` type properties.
|
|
|
|
Collection-typed attributes with the same element type will be copied by creating a new instance of the target collection type containing the elements from the source property. For collection-typed attributes with different element types each element will be mapped individually and added to the target collection (see <<mapping-collections>>).
|
|
|
|
MapStruct takes all public properties of the source and target types into account. This includes properties declared on super-types.
|
|
|
|
[[mapping-composition]]
|
|
=== Mapping Composition
|
|
MapStruct supports the use of meta annotations. The `@Mapping` annotation supports now `@Target` with `ElementType#ANNOTATION_TYPE` in addition to `ElementType#METHOD`. This allows `@Mapping` to be used on other (user defined) annotations for re-use purposes. For example:
|
|
|
|
====
|
|
[source, java, linenums]
|
|
[subs="verbatim,attributes"]
|
|
----
|
|
@Retention(RetentionPolicy.CLASS)
|
|
@Mapping(target = "id", ignore = true)
|
|
@Mapping(target = "creationDate", expression = "java(new java.util.Date())")
|
|
@Mapping(target = "name", source = "groupName")
|
|
public @interface ToEntity { }
|
|
----
|
|
====
|
|
|
|
Can be used to characterise an `Entity` without the need to have a common base type. For instance, `ShelveEntity` and `BoxEntity` do not share a common base type in the `StorageMapper` below.
|
|
====
|
|
[source, java, linenums]
|
|
[subs="verbatim,attributes"]
|
|
----
|
|
@Mapper
|
|
public interface StorageMapper {
|
|
|
|
StorageMapper INSTANCE = Mappers.getMapper( StorageMapper.class );
|
|
|
|
@ToEntity
|
|
@Mapping( target = "weightLimit", source = "maxWeight")
|
|
ShelveEntity map(ShelveDto source);
|
|
|
|
@ToEntity
|
|
@Mapping( target = "label", source = "designation")
|
|
BoxEntity map(BoxDto source);
|
|
}
|
|
----
|
|
====
|
|
|
|
Still, they do have some properties in common. The `@ToEntity` assumes both target beans `ShelveEntity` and `BoxEntity` have properties: `"id"`, `"creationDate"` and `"name"`. It furthermore assumes that the source beans `ShelveDto` and `BoxDto` always have a property `"groupName"`. This concept is also known as "duck-typing". In other words, if it quacks like duck, walks like a duck its probably a duck.
|
|
|
|
Error messages are not mature yet: the method on which the problem occurs is displayed, as well as the concerned values in the `@Mapping` annotation. However, the composition aspect is not visible. The messages are "as if" the `@Mapping` would be present on the concerned method directly.
|
|
Therefore, the user should use this feature with care, especially when uncertain when a property is always present.
|
|
|
|
A more typesafe (but also more verbose) way would be to define base classes / interfaces on the target bean and the source bean and use `@InheritConfiguration` to achieve the same result (see <<mapping-configuration-inheritance>>).
|
|
|
|
[[adding-custom-methods]]
|
|
=== Adding custom methods to mappers
|
|
|
|
In some cases it can be required to manually implement a specific mapping from one type to another which can't be generated by MapStruct. One way to handle this is to implement the custom method on another class which then is used by mappers generated by MapStruct (see <<invoking-other-mappers>>).
|
|
|
|
Alternatively, when using Java 8 or later, you can implement custom methods directly in a mapper interface as default methods. The generated code will invoke the default methods if the argument and return types match.
|
|
|
|
As an example let's assume the mapping from `Person` to `PersonDto` requires some special logic which can't be generated by MapStruct. You could then define the mapper from the previous example like this:
|
|
|
|
.Mapper which defines a custom mapping with a default method
|
|
====
|
|
[source, java, linenums]
|
|
[subs="verbatim,attributes"]
|
|
----
|
|
@Mapper
|
|
public interface CarMapper {
|
|
|
|
@Mapping(...)
|
|
...
|
|
CarDto carToCarDto(Car car);
|
|
|
|
default PersonDto personToPersonDto(Person person) {
|
|
//hand-written mapping logic
|
|
}
|
|
}
|
|
----
|
|
====
|
|
|
|
The class generated by MapStruct implements the method `carToCarDto()`. The generated code in `carToCarDto()` will invoke the manually implemented `personToPersonDto()` method when mapping the `driver` attribute.
|
|
|
|
A mapper could also be defined in the form of an abstract class instead of an interface and implement the custom methods directly in the mapper class. In this case MapStruct will generate an extension of the abstract class with implementations of all abstract methods. An advantage of this approach over declaring default methods is that additional fields could be declared in the mapper class.
|
|
|
|
The previous example where the mapping from `Person` to `PersonDto` requires some special logic could then be defined like this:
|
|
|
|
.Mapper defined by an abstract class
|
|
====
|
|
[source, java, linenums]
|
|
[subs="verbatim,attributes"]
|
|
----
|
|
@Mapper
|
|
public abstract class CarMapper {
|
|
|
|
@Mapping(...)
|
|
...
|
|
public abstract CarDto carToCarDto(Car car);
|
|
|
|
public PersonDto personToPersonDto(Person person) {
|
|
//hand-written mapping logic
|
|
}
|
|
}
|
|
----
|
|
====
|
|
|
|
MapStruct will generate a sub-class of `CarMapper` with an implementation of the `carToCarDto()` method as it is declared abstract. The generated code in `carToCarDto()` will invoke the manually implemented `personToPersonDto()` method when mapping the `driver` attribute.
|
|
|
|
[[mappings-with-several-source-parameters]]
|
|
=== Mapping methods with several source parameters
|
|
|
|
MapStruct also supports mapping methods with several source parameters. This is useful e.g. in order to combine several entities into one data transfer object. The following shows an example:
|
|
|
|
.Mapping method with several source parameters
|
|
====
|
|
[source, java, linenums]
|
|
[subs="verbatim,attributes"]
|
|
----
|
|
@Mapper
|
|
public interface AddressMapper {
|
|
|
|
@Mapping(target = "description", source = "person.description")
|
|
@Mapping(target = "houseNumber", source = "address.houseNo")
|
|
DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address);
|
|
}
|
|
----
|
|
====
|
|
|
|
The shown mapping method takes two source parameters and returns a combined target object. As with single-parameter mapping methods properties are mapped by name.
|
|
|
|
In case several source objects define a property with the same name, the source parameter from which to retrieve the property must be specified using the `@Mapping` annotation as shown for the `description` property in the example. An error will be raised when such an ambiguity is not resolved. For properties which only exist once in the given source objects it is optional to specify the source parameter's name as it can be determined automatically.
|
|
|
|
[WARNING]
|
|
====
|
|
Specifying the parameter in which the property resides is mandatory when using the `@Mapping` annotation.
|
|
====
|
|
|
|
[TIP]
|
|
====
|
|
Mapping methods with several source parameters will return `null` in case all the source parameters are `null`. Otherwise the target object will be instantiated and all properties from the provided parameters will be propagated.
|
|
====
|
|
|
|
MapStruct also offers the possibility to directly refer to a source parameter.
|
|
|
|
.Mapping method directly referring to a source parameter
|
|
====
|
|
[source, java, linenums]
|
|
[subs="verbatim,attributes"]
|
|
----
|
|
@Mapper
|
|
public interface AddressMapper {
|
|
|
|
@Mapping(target = "description", source = "person.description")
|
|
@Mapping(target = "houseNumber", source = "hn")
|
|
DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Integer hn);
|
|
}
|
|
----
|
|
====
|
|
|
|
In this case the source parameter is directly mapped into the target as the example above demonstrates. The parameter `hn`, a non bean type (in this case `java.lang.Integer`) is mapped to `houseNumber`.
|
|
|
|
[[mapping-nested-bean-properties-to-current-target]]
|
|
=== Mapping nested bean properties to current target
|
|
|
|
If you don't want explicitly name all properties from nested source bean, you can use `.` as target.
|
|
This will tell MapStruct to map every property from source bean to target object. The following shows an example:
|
|
|
|
.use of "target this" annotation "."
|
|
====
|
|
[source, java, linenums]
|
|
[subs="verbatim,attributes"]
|
|
----
|
|
@Mapper
|
|
public interface CustomerMapper {
|
|
|
|
@Mapping( target = "name", source = "record.name" )
|
|
@Mapping( target = ".", source = "record" )
|
|
@Mapping( target = ".", source = "account" )
|
|
Customer customerDtoToCustomer(CustomerDto customerDto);
|
|
}
|
|
----
|
|
====
|
|
|
|
The generated code will map every property from `CustomerDto.record` to `Customer` directly, without need to manually name any of them.
|
|
The same goes for `Customer.account`.
|
|
|
|
When there are conflicts, these can be resolved by explicitly defining the mapping. For instance in the example above. `name` occurs in `CustomerDto.record` and in `CustomerDto.account`. The mapping `@Mapping( target = "name", source = "record.name" )` resolves this conflict.
|
|
|
|
|
|
This "target this" notation can be very useful when mapping hierarchical objects to flat objects and vice versa (`@InheritInverseConfiguration`).
|
|
|
|
|
|
|
|
[[updating-bean-instances]]
|
|
=== Updating existing bean instances
|
|
|
|
In some cases you need mappings which don't create a new instance of the target type but instead update an existing instance of that type. This sort of mapping can be realized by adding a parameter for the target object and marking this parameter with `@MappingTarget`. The following shows an example:
|
|
|
|
.Update method
|
|
====
|
|
[source, java, linenums]
|
|
[subs="verbatim,attributes"]
|
|
----
|
|
@Mapper
|
|
public interface CarMapper {
|
|
|
|
void updateCarFromDto(CarDto carDto, @MappingTarget Car car);
|
|
}
|
|
----
|
|
====
|
|
|
|
The generated code of the `updateCarFromDto()` method will update the passed `Car` instance with the properties from the given `CarDto` object. There may be only one parameter marked as mapping target. Instead of `void` you may also set the method's return type to the type of the target parameter, which will cause the generated implementation to update the passed mapping target and return it as well. This allows for fluent invocations of mapping methods.
|
|
|
|
For `CollectionMappingStrategy.ACCESSOR_ONLY` Collection- or map-typed properties of the target bean to be updated will be cleared and then populated with the values from the corresponding source collection or map. Otherwise, For `CollectionMappingStrategy.ADDER_PREFERRED` or `CollectionMappingStrategy.TARGET_IMMUTABLE` the target will not be cleared and the values will be populated immediately.
|
|
|
|
[[direct-field-mappings]]
|
|
=== Mappings with direct field access
|
|
|
|
MapStruct also supports mappings of `public` fields that have no getters/setters. MapStruct will
|
|
use the fields as read/write accessor if it cannot find suitable getter/setter methods for the property.
|
|
|
|
A field is considered as a read accessor if it is `public` or `public final`. If a field is `static` it is not
|
|
considered as a read accessor.
|
|
|
|
A field is considered as a write accessor only if it is `public`. If a field is `final` and/or `static` it is not
|
|
considered as a write accessor.
|
|
|
|
Small example:
|
|
|
|
.Example classes for mapping
|
|
====
|
|
[source, java, linenums]
|
|
[subs="verbatim,attributes"]
|
|
----
|
|
public class Customer {
|
|
|
|
private Long id;
|
|
private String name;
|
|
|
|
//getters and setter omitted for brevity
|
|
}
|
|
|
|
public class CustomerDto {
|
|
|
|
public Long id;
|
|
public String customerName;
|
|
}
|
|
|
|
@Mapper
|
|
public interface CustomerMapper {
|
|
|
|
CustomerMapper INSTANCE = Mappers.getMapper( CustomerMapper.class );
|
|
|
|
@Mapping(target = "name", source = "customerName")
|
|
Customer toCustomer(CustomerDto customerDto);
|
|
|
|
@InheritInverseConfiguration
|
|
CustomerDto fromCustomer(Customer customer);
|
|
}
|
|
----
|
|
====
|
|
|
|
For the configuration from above, the generated mapper looks like:
|
|
|
|
.Generated mapper for example classes
|
|
====
|
|
[source, java, linenums]
|
|
[subs="verbatim,attributes"]
|
|
----
|
|
// GENERATED CODE
|
|
public class CustomerMapperImpl implements CustomerMapper {
|
|
|
|
@Override
|
|
public Customer toCustomer(CustomerDto customerDto) {
|
|
// ...
|
|
customer.setId( customerDto.id );
|
|
customer.setName( customerDto.customerName );
|
|
// ...
|
|
}
|
|
|
|
@Override
|
|
public CustomerDto fromCustomer(Customer customer) {
|
|
// ...
|
|
customerDto.id = customer.getId();
|
|
customerDto.customerName = customer.getName();
|
|
// ...
|
|
}
|
|
}
|
|
----
|
|
====
|
|
|
|
You can find the complete example in the
|
|
https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-field-mapping[mapstruct-examples-field-mapping]
|
|
project on GitHub.
|
|
|
|
[[mapping-with-builders]]
|
|
=== Using builders
|
|
|
|
MapStruct also supports mapping of immutable types via builders.
|
|
When performing a mapping MapStruct checks if there is a builder for the type being mapped.
|
|
This is done via the `BuilderProvider` SPI.
|
|
If a Builder exists for a certain type, then that builder will be used for the mappings.
|
|
|
|
The default implementation of the `BuilderProvider` assumes the following:
|
|
|
|
* The type has either
|
|
** A parameterless public static builder creation method that returns a builder.
|
|
e.g. `Person` has a public static method that returns `PersonBuilder`.
|
|
** A public static inner class with the name having the suffix "Builder", and a public no-args constructor
|
|
e.g. `Person` has an inner class `PersonBuilder` with a public no-args constructor.
|
|
* The builder type has a parameterless public method (build method) that returns the type being built.
|
|
In our example `PersonBuilder` has a method returning `Person`.
|
|
* In case there are multiple build methods, MapStruct will look for a method called `build`, if such method exists
|
|
then this would be used, otherwise a compilation error would be created.
|
|
* A specific build method can be defined by using `@Builder` within: `@BeanMapping`, `@Mapper` or `@MapperConfig`
|
|
* In case there are multiple builder creation methods that satisfy the above conditions then a `MoreThanOneBuilderCreationMethodException`
|
|
will be thrown from the `DefaultBuilderProvider` SPI.
|
|
In case of a `MoreThanOneBuilderCreationMethodException` MapStruct will write a warning in the compilation and not use any builder.
|
|
|
|
If such type is found then MapStruct will use that type to perform the mapping to (i.e. it will look for setters into that type).
|
|
To finish the mapping MapStruct generates code that will invoke the build method of the builder.
|
|
|
|
[NOTE]
|
|
======
|
|
Builder detection can be switched off by means of `@Builder#disableBuilder`. MapStruct will fall back on regular getters / setters in case builders are disabled.
|
|
======
|
|
|
|
[NOTE]
|
|
======
|
|
The <<object-factories>> are also considered for the builder type.
|
|
E.g. If an object factory exists for our `PersonBuilder` then this factory would be used instead of the builder creation method.
|
|
======
|
|
|
|
[NOTE]
|
|
======
|
|
Detected builders influence `@BeforeMapping` and `@AfterMapping` behavior. See <<customizing-mappings-with-before-and-after>> for more information.
|
|
======
|
|
|
|
.Person with Builder example
|
|
====
|
|
[source, java, linenums]
|
|
[subs="verbatim,attributes"]
|
|
----
|
|
public class Person {
|
|
|
|
private final String name;
|
|
|
|
protected Person(Person.Builder builder) {
|
|
this.name = builder.name;
|
|
}
|
|
|
|
public static Person.Builder builder() {
|
|
return new Person.Builder();
|
|
}
|
|
|
|
public static class Builder {
|
|
|
|
private String name;
|
|
|
|
public Builder name(String name) {
|
|
this.name = name;
|
|
return this;
|
|
}
|
|
|
|
public Person create() {
|
|
return new Person( this );
|
|
}
|
|
}
|
|
}
|
|
----
|
|
====
|
|
|
|
.Person Mapper definition
|
|
====
|
|
[source, java, linenums]
|
|
[subs="verbatim,attributes"]
|
|
----
|
|
public interface PersonMapper {
|
|
|
|
Person map(PersonDto dto);
|
|
}
|
|
----
|
|
====
|
|
|
|
.Generated mapper with builder
|
|
====
|
|
[source, java, linenums]
|
|
[subs="verbatim,attributes"]
|
|
----
|
|
// GENERATED CODE
|
|
public class PersonMapperImpl implements PersonMapper {
|
|
|
|
public Person map(PersonDto dto) {
|
|
if (dto == null) {
|
|
return null;
|
|
}
|
|
|
|
Person.Builder builder = Person.builder();
|
|
|
|
builder.name( dto.getName() );
|
|
|
|
return builder.create();
|
|
}
|
|
}
|
|
----
|
|
====
|
|
|
|
Supported builder frameworks:
|
|
|
|
* https://projectlombok.org/[Lombok] - It is required to have the Lombok classes in a separate module.
|
|
See for more information at https://github.com/rzwitserloot/lombok/issues/1538[rzwitserloot/lombok#1538] and to set up Lombok with MapStruct, refer to <<lombok>>.
|
|
* https://github.com/google/auto/blob/master/value/userguide/index.md[AutoValue]
|
|
* https://immutables.github.io/[Immutables] - When Immutables are present on the annotation processor path then the `ImmutablesAccessorNamingStrategy` and `ImmutablesBuilderProvider` would be used by default
|
|
* https://github.com/google/FreeBuilder[FreeBuilder] - When FreeBuilder is present on the annotation processor path then the `FreeBuilderAccessorNamingStrategy` would be used by default.
|
|
When using FreeBuilder then the JavaBean convention should be followed, otherwise MapStruct won't recognize the fluent getters.
|
|
* It also works for custom builders (handwritten ones) if the implementation supports the defined rules for the default `BuilderProvider`.
|
|
Otherwise, you would need to write a custom `BuilderProvider`
|
|
|
|
[TIP]
|
|
====
|
|
In case you want to disable using builders then you can pass the MapStruct processor option `mapstruct.disableBuilders` to the compiler. e.g. `-Amapstruct.disableBuilders=true`.
|
|
====
|
|
|
|
[[mapping-with-constructors]]
|
|
=== Using Constructors
|
|
|
|
MapStruct supports using constructors for mapping target types.
|
|
When doing a mapping MapStruct checks if there is a builder for the type being mapped.
|
|
If there is no builder, then MapStruct looks for a single accessible constructor.
|
|
When there are multiple constructors then the following is done to pick the one which should be used:
|
|
|
|
* If a constructor is annotated with an annotation _named_ `@Default` (from any package, see <<non-shipped-annotations>>) it will be used.
|
|
* If a single public constructor exists then it will be used to construct the object, and the other non public constructors will be ignored.
|
|
* If a parameterless constructor exists then it will be used to construct the object, and the other constructors will be ignored.
|
|
* If there are multiple eligible constructors then there will be a compilation error due to ambiguous constructors. In order to break the ambiguity an annotation _named_ `@Default` (from any package, see <<non-shipped-annotations>>) can used.
|
|
|
|
.Deciding which constructor to use
|
|
====
|
|
[source, java, linenums]
|
|
[subs="verbatim,attributes"]
|
|
----
|
|
public class Vehicle {
|
|
|
|
protected Vehicle() { }
|
|
|
|
// MapStruct will use this constructor, because it is a single public constructor
|
|
public Vehicle(String color) { }
|
|
}
|
|
|
|
public class Car {
|
|
|
|
// MapStruct will use this constructor, because it is a parameterless empty constructor
|
|
public Car() { }
|
|
|
|
public Car(String make, String color) { }
|
|
}
|
|
|
|
public class Truck {
|
|
|
|
public Truck() { }
|
|
|
|
// MapStruct will use this constructor, because it is annotated with @Default
|
|
@Default
|
|
public Truck(String make, String color) { }
|
|
}
|
|
|
|
public class Van {
|
|
|
|
// There will be a compilation error when using this class because MapStruct cannot pick a constructor
|
|
|
|
public Van(String make) { }
|
|
|
|
public Van(String make, String color) { }
|
|
|
|
}
|
|
----
|
|
====
|
|
|
|
When using a constructor then the names of the parameters of the constructor will be used and matched to the target properties.
|
|
When the constructor has an annotation _named_ `@ConstructorProperties` (from any package, see <<non-shipped-annotations>>) then this annotation will be used to get the names of the parameters.
|
|
|
|
[NOTE]
|
|
====
|
|
When an object factory method or a method annotated with `@ObjectFactory` exists, it will take precedence over any constructor defined in the target.
|
|
The target object constructor will not be used in that case.
|
|
====
|
|
|
|
|
|
.Person with constructor parameters
|
|
====
|
|
[source, java, linenums]
|
|
[subs="verbatim,attributes"]
|
|
----
|
|
public class Person {
|
|
|
|
private final String name;
|
|
private final String surname;
|
|
|
|
public Person(String name, String surname) {
|
|
this.name = name;
|
|
this.surname = surname;
|
|
}
|
|
}
|
|
----
|
|
====
|
|
|
|
.Person With Constructor Mapper definition
|
|
====
|
|
[source, java, linenums]
|
|
[subs="verbatim,attributes"]
|
|
----
|
|
public interface PersonMapper {
|
|
|
|
Person map(PersonDto dto);
|
|
}
|
|
----
|
|
====
|
|
|
|
.Generated mapper with constructor
|
|
====
|
|
[source, java, linenums]
|
|
[subs="verbatim,attributes"]
|
|
----
|
|
// GENERATED CODE
|
|
public class PersonMapperImpl implements PersonMapper {
|
|
|
|
public Person map(PersonDto dto) {
|
|
if (dto == null) {
|
|
return null;
|
|
}
|
|
|
|
String name;
|
|
String surname;
|
|
name = dto.getName();
|
|
surname = dto.getSurname();
|
|
|
|
Person person = new Person( name, surname );
|
|
|
|
return person;
|
|
}
|
|
}
|
|
----
|
|
====
|
|
|
|
[[mapping-map-to-bean]]
|
|
=== Mapping Map to Bean
|
|
|
|
There are situations when a mapping from a `Map<String, ???>` into a specific bean is needed.
|
|
MapStruct offers a transparent way of doing such a mapping by using the target bean properties (or defined through `Mapping#source`) to extract the values from the map.
|
|
Such a mapping looks like:
|
|
|
|
.Example classes for mapping map to bean
|
|
====
|
|
[source, java, linenums]
|
|
[subs="verbatim,attributes"]
|
|
----
|
|
public class Customer {
|
|
|
|
private Long id;
|
|
private String name;
|
|
|
|
//getters and setter omitted for brevity
|
|
}
|
|
|
|
@Mapper
|
|
public interface CustomerMapper {
|
|
|
|
@Mapping(target = "name", source = "customerName")
|
|
Customer toCustomer(Map<String, String> map);
|
|
|
|
}
|
|
----
|
|
====
|
|
|
|
.Generated mapper for mapping map to bean
|
|
====
|
|
[source, java, linenums]
|
|
[subs="verbatim,attributes"]
|
|
----
|
|
// GENERATED CODE
|
|
public class CustomerMapperImpl implements CustomerMapper {
|
|
|
|
@Override
|
|
public Customer toCustomer(Map<String, String> map) {
|
|
// ...
|
|
if ( map.containsKey( "id" ) ) {
|
|
customer.setId( Integer.parseInt( map.get( "id" ) ) );
|
|
}
|
|
if ( map.containsKey( "customerName" ) ) {
|
|
customer.setName( map.get( "customerName" ) );
|
|
}
|
|
// ...
|
|
}
|
|
}
|
|
----
|
|
====
|
|
|
|
[NOTE]
|
|
====
|
|
All existing rules about mapping between different types and using other mappers defined with `Mapper#uses` or custom methods in the mappers are applied.
|
|
i.e. You can map from `Map<String, Integer>` where for each property a conversion from `Integer` into the respective property will be needed.
|
|
====
|
|
|
|
[WARNING]
|
|
====
|
|
When a raw map or a map that does not have a String as a key is used, then a warning will be generated.
|
|
The warning is not generated if the map itself is mapped into some other target property directly as is.
|
|
====
|
|
|
|
[[adding-annotations]]
|
|
=== Adding annotations
|
|
|
|
Other frameworks sometimes requires you to add annotations to certain classes so that they can easily detect the mappers.
|
|
Using the `@AnnotateWith` annotation you can generate an annotation at the specified location.
|
|
|
|
For example Apache Camel has a `@Converter` annotation which you can apply to generated mappers using the `@AnnotateWith` annotation.
|
|
|
|
.AnnotateWith source example
|
|
====
|
|
[source, java, linenums]
|
|
[subs="verbatim,attributes"]
|
|
----
|
|
@Mapper
|
|
@AnnotateWith(
|
|
value = Converter.class,
|
|
elements = @AnnotateWith.Element( name = "generateBulkLoader", booleans = true )
|
|
)
|
|
public interface MyConverter {
|
|
@AnnotateWith( Converter.class )
|
|
DomainObject map( DtoObject dto );
|
|
}
|
|
----
|
|
====
|
|
|
|
.AnnotateWith generated mapper
|
|
====
|
|
[source, java, linenums]
|
|
[subs="verbatim,attributes"]
|
|
----
|
|
@Converter( generateBulkLoader = true )
|
|
public class MyConverterImpl implements MyConverter {
|
|
@Converter
|
|
public DomainObject map( DtoObject dto ) {
|
|
// default mapping behaviour
|
|
}
|
|
}
|
|
----
|
|
====
|
|
|
|
|
|
[[javadoc]]
|
|
=== Adding Javadoc comments
|
|
|
|
MapStruct provides support for defining Javadoc comments in the generated mapper implementation using the
|
|
`org.mapstruct.Javadoc` annotation.
|
|
|
|
This functionality could be relevant especially in situations where certain Javadoc standards need to be met or
|
|
to deal with Javadoc validation constraints.
|
|
|
|
The `@Javadoc` annotation defines attributes for the different Javadoc elements.
|
|
|
|
Consider the following example:
|
|
|
|
.Javadoc annotation example
|
|
====
|
|
[source, java, linenums]
|
|
[subs="verbatim,attributes"]
|
|
----
|
|
@Mapper
|
|
@Javadoc(
|
|
value = "This is the description",
|
|
authors = { "author1", "author2" },
|
|
deprecated = "Use {@link OtherMapper} instead",
|
|
since = "0.1"
|
|
)
|
|
public interface MyAnnotatedWithJavadocMapper {
|
|
//...
|
|
}
|
|
----
|
|
====
|
|
|
|
.Javadoc annotated generated mapper
|
|
====
|
|
[source, java, linenums]
|
|
[subs="verbatim,attributes"]
|
|
----
|
|
/**
|
|
* This is the description
|
|
*
|
|
* @author author1
|
|
* @author author2
|
|
*
|
|
* @deprecated Use {@link OtherMapper} instead
|
|
* @since 0.1
|
|
*/
|
|
public class MyAnnotatedWithJavadocMapperImpl implements MyAnnotatedWithJavadocMapper {
|
|
//...
|
|
}
|
|
----
|
|
====
|
|
|
|
The entire Javadoc comment block can be provided directly as well.
|
|
|
|
.Javadoc annotation example with the entire Javadoc comment block provided directly
|
|
====
|
|
[source, java, linenums]
|
|
[subs="verbatim,attributes"]
|
|
----
|
|
@Mapper
|
|
@Javadoc(
|
|
"This is the description\n"
|
|
+ "\n"
|
|
+ "@author author1\n"
|
|
+ "@author author2\n"
|
|
+ "\n"
|
|
+ "@deprecated Use {@link OtherMapper} instead\n"
|
|
+ "@since 0.1\n"
|
|
)
|
|
public interface MyAnnotatedWithJavadocMapper {
|
|
//...
|
|
}
|
|
----
|
|
====
|
|
|
|
Or using Text blocks:
|
|
|
|
.Javadoc annotation example with the entire Javadoc comment block provided directly using Text blocks
|
|
====
|
|
[source, java, linenums]
|
|
[subs="verbatim,attributes"]
|
|
----
|
|
@Mapper
|
|
@Javadoc(
|
|
"""
|
|
This is the description
|
|
|
|
@author author1
|
|
@author author2
|
|
|
|
@deprecated Use {@link OtherMapper} instead
|
|
@since 0.1
|
|
"""
|
|
)
|
|
public interface MyAnnotatedWithJavadocMapper {
|
|
//...
|
|
}
|
|
----
|
|
==== |