Development background
Have you ever encountered such a development scenario?
Services provide data externally through interfaces or conduct data interaction between services. First, query the database and map it into a data object (XdO).
Under normal circumstances, the interface is not allowed to directly provide data in the form of database data object XxxDO, but to be encapsulated into data transmission object (XxxDTO).
Why not provide DO directly?
1) According to the single design principle, DO can only correspond to data entity objects and cannot undertake other responsibilities;
2) DO may contain all field data in the table, which does not comply with the parameter definition of the interface. If the data is too large, it will affect the transmission speed and DO not comply with the data security principle;
3) According to< Ali Java development manual "Layered domain model specification, can not be an object to go global, need to be defined as POJO/DO/BO/DTO/VO/Query and other data objects, the complete definition can refer to Ali development manual, pay attention to the official account: Java technology stack, in the background reply: manual, you can get the latest HD full version.
Traditional do - > dto approach
XxxDTO may contain most of the data of XxxDO or combine some data of other DO S. The traditional methods are as follows:
-
get/ set
-
constructor
-
BeanUtils tool class
-
Builder mode
I believe most people's practice is like this. Although it is very direct, it is generally really Low and has strong coupling. They often lose parameters or make wrong parameter values. In this development scenario, I personally think these are not the best way.
This kind of development scenario is too common. Is there an automatic mapping tool for Java bean s?
Yes - exactly MapStruct!!
Introduction to MapStruct
Official website address:
https://mapstruct.org/
Open source address:
https://github.com/mapstruct/mapstruct
Java bean mappings, the easy way!
Java bean mapping is done in a simple way.
MapStruct is a code generator. Like Spring Boot and Maven, it is also based on the concept of convention over configuration, which greatly simplifies the implementation of data mapping between Java bean s.
Advantages of MapStruct:
1. MapStruct uses simple method calls to generate mapping code, so * * * speed is very fast * * *;
2. Type safety, avoid errors, and can only map objects and attributes mapped to each other, so user entities will not be incorrectly mapped to order DTO;
3. Only JDK 1.8 + is required, without any other dependencies, and all codes are included;
4. Easy to debug;
5. Easy to understand;
Supported methods:
MapStruct supports command-line compilation, such as pure javac commands, Maven, Gradle, Ant, etc. it also supports IDEs such as Eclipse and IntelliJ IDEA.
MapStruct actual combat
In this paper, the stack length is demonstrated based on IntelliJ IDEA, Spring Boot and Maven.
Basic preparation
Add two database DO classes:
A user main class and a user extension class.
/** * WeChat official account: Java technology stack * @author Stack length */ @Data public class UserDO { private String name; private int sex; private int age; private Date birthday; private String phone; private boolean married; private Date regDate; private Date loginDate; private String memo; private UserExtDO userExtDO; }
/** * WeChat official account: Java technology stack * @author Stack length */ @Data public class UserExtDO { private String regSource; private String favorite; private String school; private int kids; private String memo; }
Add a DTO class for data transmission:
User presentation class, including some data of user main class and user extension class.
/** * WeChat official account: Java technology stack * @author Stack length */ @Data public class UserShowDTO { private String name; private int sex; private boolean married; private String birthday; private String regDate; private String registerSource; private String favorite; private String memo; }
Start actual combat
Here's the point. Don't get/set or BeanUtils. How can you encapsulate the data of two user objects into DTO objects?
Spring Boot basics will not be introduced in this article. A series of basic tutorials and sample source codes can be seen here: https://github.com/javastacks/spring-boot-best-practice
Introducing MapStruct dependency:
<dependencies> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>${org.mapstruct.version}</version> </dependency> </dependencies>
Maven plug-in configuration:
The combination of MapStruct and Lombok will cause version conflict. Please pay attention to the following configuration.
<build> <plugins> <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>${org.mapstruct.version}</version> </path> <!-- use Lombok Need to add --> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${org.projectlombok.version}</version> </path> <!-- Lombok 1.18.16 And above need to be added, otherwise an error will be reported --> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok-mapstruct-binding</artifactId> <version>${lombok-mapstruct-binding.version}</version> </path> </annotationProcessorPaths> </configuration> </plugin> </plugins> </build>
Add MapStruct mapping:
/** * WeChat official account: Java technology stack * @author Stack length */ @Mapper public interface UserStruct { UserStruct INSTANCE = Mappers.getMapper(UserStruct.class); @Mappings({ @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd") @Mapping(target = "regDate", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(userDO.getRegDate(),\"yyyy-MM-dd HH:mm:ss\"))") @Mapping(source = "userExtDO.regSource", target = "registerSource") @Mapping(source = "userExtDO.favorite", target = "favorite") @Mapping(target = "memo", ignore = true) }) UserShowDTO toUserShowDTO(UserDO userDO); List<UserShowDTO> toUserShowDTOs(List<UserDO> userDOs); }
Key notes:
1) Add an interface interface and use the @ Mapper annotation of MapStruct to decorate it. Here, it is named xxstruct so as not to be confused with Mapper of MyBatis;
2) Use Mappers to add an INSTANCE or Spring injection, which will be discussed later;
3) Add two mapping methods to return a single object and object list;
4) Use @ Mappings + @Mapping combination mapping. If the two field names are the same, you can not write them. You can specify the date format, number format, expression, etc. of the mapping. Ignore means to ignore the field mapping;
5) The mapping of the List method will call a single method mapping without separate mapping. You can see the source code later;
In addition, java version 8 + and above do not need @ Mappings annotation. Just use @ Mapping annotation directly:
After Java 8 modification:
/** * WeChat official account: Java technology stack * @author Stack length */ @Mapper public interface UserStruct { UserStruct INSTANCE = Mappers.getMapper(UserStruct.class); @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd") @Mapping(target = "regDate", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(userDO.getRegDate(),\"yyyy-MM-dd HH:mm:ss\"))") @Mapping(source = "userExtDO.regSource", target = "registerSource") @Mapping(source = "userExtDO.favorite", target = "favorite") @Mapping(target = "memo", ignore = true) UserShowDTO toUserShowDTO(UserDO userDO); List<UserShowDTO> toUserShowDTOs(List<UserDO> userDOs); }
Test:
/** * WeChat official account: Java technology stack * @author Stack length */ public class UserStructTest { @Test public void test1() { UserExtDO userExtDO = new UserExtDO(); userExtDO.setRegSource("Official account: Java Technology stack"); userExtDO.setFavorite("Write code"); userExtDO.setSchool("Social University"); UserDO userDO = new UserDO(); userDO.setName("Stack length"); userDO.setSex(1); userDO.setAge(18); userDO.setBirthday(new Date()); userDO.setPhone("18888888888"); userDO.setMarried(true); userDO.setRegDate(new Date()); userDO.setMemo("666"); userDO.setUserExtDO(userExtDO); UserShowDTO userShowDTO = UserStruct.INSTANCE.toUserShowDTO(userDO); System.out.println("=====Single object mapping====="); System.out.println(userShowDTO); List<UserDO> userDOs = new ArrayList<>(); UserDO userDO2 = new UserDO(); BeanUtils.copyProperties(userDO, userDO2); userDO2.setName("Stack length 2"); userDOs.add(userDO); userDOs.add(userDO2); List<UserShowDTO> userShowDTOs = UserStruct.INSTANCE.toUserShowDTOs(userDOs); System.out.println("=====Object list mapping====="); userShowDTOs.forEach(System.out::println); } }
Output results:
Look at the results. The data conversion results are successful.
What principle?
As we know above, you can modify the interface through an annotation. What is the principle?
Look at the compiled Directory:
The principle is that an implementation class of the interface is generated during compilation.
Open the source code:
public class UserStructImpl implements UserStruct { public UserStructImpl() { } public UserShowDTO toUserShowDTO(UserDO userDO) { if (userDO == null) { return null; } else { UserShowDTO userShowDTO = new UserShowDTO(); if (userDO.getBirthday() != null) { userShowDTO.setBirthday((new SimpleDateFormat("yyyy-MM-dd")).format(userDO.getBirthday())); } userShowDTO.setRegisterSource(this.userDOUserExtDORegSource(userDO)); userShowDTO.setFavorite(this.userDOUserExtDOFavorite(userDO)); userShowDTO.setName(userDO.getName()); userShowDTO.setSex(userDO.getSex()); userShowDTO.setMarried(userDO.isMarried()); userShowDTO.setRegDate(DateFormatUtils.format(userDO.getRegDate(), "yyyy-MM-dd HH:mm:ss")); return userShowDTO; } } public List<UserShowDTO> toUserShowDTOs(List<UserDO> userDOs) { if (userDOs == null) { return null; } else { List<UserShowDTO> list = new ArrayList(userDOs.size()); Iterator var3 = userDOs.iterator(); while(var3.hasNext()) { UserDO userDO = (UserDO)var3.next(); list.add(this.toUserShowDTO(userDO)); } return list; } } private String userDOUserExtDORegSource(UserDO userDO) { if (userDO == null) { return null; } else { UserExtDO userExtDO = userDO.getUserExtDO(); if (userExtDO == null) { return null; } else { String regSource = userExtDO.getRegSource(); return regSource == null ? null : regSource; } } } private String userDOUserExtDOFavorite(UserDO userDO) { if (userDO == null) { return null; } else { UserExtDO userExtDO = userDO.getUserExtDO(); if (userExtDO == null) { return null; } else { String favorite = userExtDO.getFavorite(); return favorite == null ? null : favorite; } } } }
In fact, the implementation class calls other routine operations such as get/set of the object, and the List is a single mapping method of the object that is called circularly. That's clear!
Spring injection method
The above example creates a UserStruct instance:
UserStruct INSTANCE = Mappers.getMapper(UserStruct.class);
As shown in @ Mapper annotation source code:
The default value of the parameter componentModel is default, that is, the instance can be created manually or injected through Spring.
The modified version of Spring is as follows:
After killing the INSTANCE, the @ Mapper annotation adds the value of componentModel = "spring".
/** * WeChat official account: Java technology stack * @author Stack length */ @Mapper(componentModel = "spring") public interface UserSpringStruct { @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd") @Mapping(target = "regDate", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(userDO.getRegDate(),\"yyyy-MM-dd HH:mm:ss\"))") @Mapping(source = "userExtDO.regSource", target = "registerSource") @Mapping(source = "userExtDO.favorite", target = "favorite") @Mapping(target = "memo", ignore = true) UserShowDTO toUserShowDTO(UserDO userDO); List<UserShowDTO> toUserShowDTOs(List<UserDO> userDOS); }
Test:
Spring boot is used in this article, so the unit test method of spring boot is used here. Spring Boot unit test can not be concerned about the official account: Java technology stack, in the background reply: boot, series of tutorials are sorted out.
/** * WeChat official account: Java technology stack * @author Stack length */ @RunWith(SpringRunner.class) @SpringBootTest public class UserSpringStructTest { @Autowired private UserSpringStruct userSpringStruct; @Test public void test1() { UserExtDO userExtDO = new UserExtDO(); userExtDO.setRegSource("Official account: Java Technology stack"); userExtDO.setFavorite("Write code"); userExtDO.setSchool("Social University"); UserDO userDO = new UserDO(); userDO.setName("Stack length Spring"); userDO.setSex(1); userDO.setAge(18); userDO.setBirthday(new Date()); userDO.setPhone("18888888888"); userDO.setMarried(true); userDO.setRegDate(new Date()); userDO.setMemo("666"); userDO.setUserExtDO(userExtDO); UserShowDTO userShowDTO = userSpringStruct.toUserShowDTO(userDO); System.out.println("=====Single object mapping====="); System.out.println(userShowDTO); List<UserDO> userDOs = new ArrayList<>(); UserDO userDO2 = new UserDO(); BeanUtils.copyProperties(userDO, userDO2); userDO2.setName("Stack length Spring2"); userDOs.add(userDO); userDOs.add(userDO2); List<UserShowDTO> userShowDTOs = userSpringStruct.toUserShowDTOs(userDOs); System.out.println("=====Object list mapping====="); userShowDTOs.forEach(System.out::println); } }
As shown above, use directly @ Autowired injection is OK, which is more convenient to use.
Output results:
No problem, stable as a dog.