Preface
In our daily development of hierarchical applications, in order to decouple each layer from each other, different objects are generally defined to transfer data between different layers. Therefore, there are various XXXDTO
, XXXVO
, XXXBO
For objects derived from database objects, when transferring data between different layers, it is inevitable that these objects often need to be converted to each other.
At this time, there are generally two processing methods: ① directly use the Setter
and Getter
methods to convert, ② use some tools for conversion (eg BeanUtil.copyProperties
). In the first way, if there are many object attributes, you need to write a lot of code Getter/Setter
Although the second method seems to be much simpler than the first method, because it uses reflection, the performance is not very good, and there are many pitfalls in use. MapStruct to be introduced today solves the shortcomings of these two methods without affecting performance.
What is MapStruct
MapStruct
is a code generator, which is based on the convention over the configuration method, which greatly simplifies the implementation of the mapping between Java bean
The automatically generated mapping conversion code only uses simple method calls, so it is fast, type-safe and easy to understand and read. The address of the Github
MapStruct . In general, there are three characteristics as follows:
- Annotation-based
- Automatically generate mapping conversion code at compile time
- Type safety, high performance, no dependencies
MapStruct use steps
MapStruct
is relatively simple, only the following three steps are required.
① Introduce dependency (here we take Gradle
as an example)
dependencies {
implementation 'org.mapstruct:mapstruct:1.4.2.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'
}
② Create related conversion objects
/**
* @author mghio
* @since 2021-08-08
*/
@Data
public class Doctor {
private Integer id;
private String name;
}
/**
* @author mghio
* @since 2021-08-08
*/
@Data
public class DoctorDTO {
private Integer id;
private String name;
}
③ Create a converter class (Mapper)
It should be noted that the converter does not necessarily have to Mapper
, but the official example recommends XXXMapper
Here is the simplest mapping case (the field name and type are exactly matched), just need to @Mapper
add 06110852955118 annotation to the converter class, the converter code is as follows:
/**
* @author mghio
* @since 2021-08-08
*/
@Mapper
public interface DoctorMapper {
DoctorMapper INSTANCE = Mappers.getMapper(DoctorMapper.class);
DoctorDTO toDTO(Doctor doctor);
}
The following simple test is used to verify whether the conversion result is correct. The test code is as follows:
/**
* @author mghio
* @since 2021-08-08
*/
public class DoctorTest {
@Test
public void testToDTO() {
Integer doctorId = 9527;
String doctorName = "mghio";
Doctor doctor = new Doctor();
doctor.setId(doctorId);
doctor.setName(doctorName);
DoctorDTO doctorDTO = DoctorMapper.INSTANCE.toDTO(doctor);
assertEquals(doctorId, doctorDTO.getId());
assertEquals(doctorName, doctorDTO.getName());
}
}
The test results passed normally, indicating that the use of the DoctorMapper
converter has achieved our expected results.
Analysis of MapStruct Implementation
In the above example, using MapStruct
to achieve the Doctor
to DoctorDTO
in three simple steps, then, MapStruct
do it? In fact, through the converter we defined, we can find that the converter is of the interface type, and we know that in Java
, the interface cannot provide functions, but the specification is defined, and the specific work is still its implementation class.
Therefore, we can boldly guess that MapStruct
must have generated an implementation class for our defined converter interface ( DoctorMapper
Mappers.getMapper(DoctorMapper.class)
actually obtained the implementation class of the converter interface. The following is verified debug
in the test class:
debug
can be seen from DoctorMapper.INSTANCE
obtains the interface implementation class DoctorMapperImpl
. This converter interface implementation class is automatically generated compile time Gradle
project is under build/generated/sources/anotationProcessor/Java
(the Maven
project is under the target/generated-sources/annotations
directory). The implementation class source code for the above example converter interface is generated as follows:
It can be found that the automatically generated code is similar to what we usually write by hand. It is simple and easy to understand. The code is generated during compilation and has no runtime dependencies. Compared with the implementation using reflection, there is another advantage that it is easy to go to debug
implement the source code to locate when an error occurs, while the reflection is relatively more difficult to locate.
Introduction to common usage scenarios
① The object attribute name and type are exactly the same
As can be seen from the above example, when the attribute name and type are exactly the same, we only need to define a converter interface and add the @Mapper
annotation, and then MapStruct
will automatically generate the implementation class to complete the conversion. The sample code is as follows:
/**
* @author mghio
* @since 2021-08-08
*/
@Data
public class Source {
private Integer id;
private String name;
}
/**
* @author mghio
* @since 2021-08-08
*/
@Data
public class Target {
private Integer id;
private String name;
}
/**
* @author mghio
* @since 2021-08-08
*/
@Mapper
public interface SourceMapper {
SourceMapper INSTANCE = Mappers.getMapper(SourceMapper.class);
Target toTarget(Source source);
}
② The object attribute type is the same but the name is different
When the object attribute type is the same but the attribute name is different, you can manually specify the conversion @Mapping
The sample code is as follows:
/**
* @author mghio
* @since 2021-08-08
*/
@Data
public class Source {
private Integer id;
private String sourceName;
}
/**
* @author mghio
* @since 2021-08-08
*/
@Data
public class Target {
private Integer id;
private String targetName;
}
/**
* @author mghio
* @since 2021-08-08
*/
@Mapper
public interface SourceMapper {
SourceMapper INSTANCE = Mappers.getMapper(SourceMapper.class);
@Mapping(source = "sourceName", target = "targetName")
Target toTarget(Source source);
}
③ Use custom conversion method in Mapper
Sometimes, for certain types (for example: a class whose attributes are custom classes), it cannot be processed in the form of automatically generated code. At this point we need methods to customize the type of conversion, in JDK 7
previous version, you need to use an abstract class to define the conversion Mapper
, and in JDK 8
can use the interface above version default method from the method defined type conversion. The sample code is as follows:
/**
* @author mghio
* @since 2021-08-08
*/
@Data
public class Source {
private Integer id;
private String sourceName;
private InnerSource innerSource;
}
/**
* @author mghio
* @since 2021-08-08
*/
@Data
public class InnerSource {
private Integer deleted;
private String name;
}
/**
* @author mghio
* @since 2021-08-08
*/
@Data
public class Target {
private Integer id;
private String targetName;
private InnerTarget innerTarget;
}
/**
* @author mghio
* @since 2021-08-08
*/
@Data
public class InnerTarget {
private Boolean isDeleted;
private String name;
}
/**
* @author mghio
* @since 2021-08-08
*/
@Mapper
public interface SourceMapper {
SourceMapper INSTANCE = Mappers.getMapper(SourceMapper.class);
@Mapping(source = "sourceName", target = "targetName")
Target toTarget(Source source);
default InnerTarget innerTarget2InnerSource(InnerSource innerSource) {
if (innerSource == null) {
return null;
}
InnerTarget innerTarget = new InnerTarget();
innerTarget.setIsDeleted(innerSource.getDeleted() == 1);
innerTarget.setName(innerSource.getName());
return innerTarget;
}
}
④ Convert multiple objects into one object and return
In the process of some actual business coding, it is inevitable that multiple objects need to be converted into one object. MapStruct
can also support it well. For this kind of final return information from multiple classes, we can achieve it through configuration Many-to-one conversion. The sample code is as follows:
/**
* @author mghio
* @since 2021-08-08
*/
@Data
public class Doctor {
private Integer id;
private String name;
}
/**
* @author mghio
* @since 2021-08-09
*/
@Data
public class Address {
private String street;
private Integer zipCode;
}
/**
* @author mghio
* @since 2021-08-09
*/
@Mapper
public interface AddressMapper {
AddressMapper INSTANCE = Mappers.getMapper(AddressMapper.class);
@Mapping(source = "doctor.id", target = "personId")
@Mapping(source = "address.street", target = "streetDesc")
DeliveryAddressDTO doctorAndAddress2DeliveryAddressDTO(Doctor doctor, Address address);
}
From the converter ( AddressMapper
) in this example, it can be seen that when the attribute name and type exactly match, it can also be automatically converted, but when the source object has multiple attribute names and types that are exactly the same as the target object, you still need to manually configure and specify MapStruct
, because 0611085295560a cannot accurately determine which attribute conversion should be used at this time.
Several ways to obtain the converter (Mapper)
The way to obtain the converter is different according to the @Mapper
annotation of componentModel
, and supports the following four different values:
- default default method, the default method, use the factory method (
Mappers.getMapper(Class)
) to get - cdi The mapper generated at this time is
CDI bean
scope of the application, use the@Inject
annotation to get - spring
Spring
can be obtained through the@Autowired
annotation, it is recommended to use this methodSpring
- jsr330 generated maps Used
@javax.inject.Named
and@Singleton
notes, by@Inject
to get
① Obtained through factory
In the example above are obtained by way of the plant, is used MapStruct
provided Mappers.getMapper(Class<T> clazz)
method to obtain the specified type Mapper
. Then when calling, there is no need to create objects repeatedly. The final implementation of the method is to load MapStruct
through the class loader that defines the interface (the class name rule is: interface name + Impl
), and then call the class of no Create the object with the constructor. The core source code is as follows:
② Use dependency injection to obtain
For dependency injection ( dependency injection
), Spring
framework for development should be familiar with it, and it is often used in work. MapStruct
also supports the use of dependency injection, and the official recommendation is to use dependency injection to obtain it. Use Spring
dependent manner only need to specify injection @Mapper
annotations componentModel = "spring"
to, the following sample code:
/**
* @author mghio
* @since 2021-08-08
*/
@Mapper(componentModel = "spring")
public interface SourceMapper {
SourceMapper INSTANCE = Mappers.getMapper(SourceMapper.class);
@Mapping(source = "sourceName", target = "targetName")
Target toTarget(Source source);
}
The reason we can use @Autowired
SourceMapper
the implementation class of the 061108529558af interface has been registered as a Bean
in the container. You can also see the code of the interface implementation class generated as follows, and the @Component
annotation is automatically added to the class.
Finally, there are two considerations: ① When two attributes convert object is inconsistent (for example, DoctorDTO
does not exist Doctor
objects in a field), there will be warning when compiling. You can configure ignore = true
@Mapping
annotation, or when there are many inconsistent fields, you can directly set the unmappedTargetPolicy
attribute of the @Mapper
unmappedSourcePolicy
attribute to ReportingPolicy.IGNORE
. Lombok is also used in your project, please note that the version of Lombok
1.18.10
or above, otherwise the compilation will fail. I also stepped on this pit when I first started using it. . .
Summarize
This article introduces the object conversion tool Mapstruct
library to reduce our conversion code in a safe and elegant way. As can be seen from the examples in the article, Mapstruct
provides a large number of functions and configurations, allowing us to create simple to complex mappers in a simple and quick way. What is introduced in the article is only Mapstruct
library, and there are many powerful functions that are not mentioned in the article. Interested friends can check the official user guide .
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。