As we all know, as a project becomes more and more mature, module division will be more and more detailed, in which the entity class is generally stored in the domain, but the domain project should not be depended on by other projects, so when other projects want to obtain the entity class data, they need to write model in their own projects. Custom models can map entity attributes based on their business needs. So the mapping project doesn’t look easy. Sam almost got confused…

sequence

Mapstruct is a plugin for domin entity class and model class attribute mapping. We just need to define mapper interface. Mapstruct will automatically help us to implement this mapping interface when compiling. Avoid cumbersome and complex mapping implementations.

That’s what some of you might be asking, right? Why not use BeanUtils’ copyProperties method? Isn’t it possible to map attributes as well?

Amiao, I was also curious at first, so I talked to BeanUtils in depth. Finally, I realized that BeanUtils is a very stupid object that can only be mapped to attributes, or allows fewer attributes to be mapped if the attributes are the same. However, when the mapped attribute data type is changed or the mapped field name is changed, the mapping fails. Mapstruct, on the other hand, is a wonderful wife. She thinks of everything that might happen to us. (I wish I could have a wife like that, laughing inside.)

Here is the open source project address and various examples of the plugin:

  • Github address: github.com/mapstruct/m…
  • Example: github.com/mapstruct/m…

First, preparation

Next, Amiao will join the rest of us in unmasking the beautiful Lady’s true veil, so we need to do a little preparation.

1.1. Understand @mapper annotations

The @mapper annotation was added from Mybatis3.4.0 in order to stop writing Mapper mapping files.

We can write SQL statements using only annotations on the dao layer defined interface, for example:

@Select("select * from user where name = #{name}")
public User find(String name);
Copy the code

This is a simple use that, while simple, demonstrates the advantage of this annotation, at least one less XML file is written.

But Amiao I don’t want to talk to you about @Mapper annotations today. I mostly want to see my wonderful wife, Mapstruct, so I just want to talk about @Mapper’s componentModel properties. The componentModel property specifies the component type of the automatically generated interface implementation class. This property supports four values:

  • Default: This is the default, mapstruct does not use any component types and can get automatically generated instance objects using Mappers.getmapper (Class).
  • cdi: the generated mapper is an application-scoped CDI bean and can be retrieved via @Inject
  • Spring: An @Component annotation is automatically added to the generated implementation class, which can be injected via Spring’s @Autowired method
  • Jsr330: The generated implementation class will add @javax.inject.Named and @Singleton annotations, which can be obtained by the @Inject annotation

1.2. Dependency packages

Import the dependency package, which consists of two packages:

  • Org. mapstruct:mapstruct: contains some necessary annotations, such as @mapping. Mapstruct :mapstruct-jdk8. This will help us take advantage of some of the new Java8 features.

  • Mapstruct processor, which automatically generates an implementation of mapper based on annotations.

    <dependency> <groupId>org.mapstruct</groupId> <! Mapstruct -jdk8</artifactId> <version> 1.2.0.final </version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> < version > 1.2.0. Final < / version > < / dependency >Copy the code

All right, that’s all we need to do. Now let’s find out where Darling happens to be.

Two, first a simple play

2.1. Define entity classes and mapped classes

Constructor @constructor @constructor @builder public class User {private Integer id; private String name; private String createTime; private LocalDateTime updateTime; } @data@noargsconstructor @allargsconstructor @builder public class UserVO1 {private Integer id; private String name; private String createTime; private LocalDateTime updateTime; } @data@noargsconstructor @allargsconstructor @builder public class UserVO2 {private Integer id; private String name; private String createTime; }Copy the code

2.2. Define interfaces:

When the entity class and the mapped object have the same attributes or the mapped object has fewer attributes:

@Mapper(componentModel = "spring") public interface UserCovertBasic { UserCovertBasic INSTANCE = Mappers.getMapper(UserCovertBasic.class); * @param source * @return */ UserVO1 toConvertVO1(User source); User fromConvertEntity1(UserVO1 userVO1); FromConvertEntity2 * @param source * @return */ UserVO2 toConvertVO2(User source); }Copy the code

INSTANCE is a member variable that allows clients to access the Mapper interface implementation.

2.3, use,

@RestController public class TestController { @GetMapping("convert") public Object convertEntity() { User user = User.builder().id(1).name(" 三").createTime("2020-04-01 11:05:07").updateTime(localDateTime.now ()).build(); List<Object> objectList = new ArrayList<>(); objectList.add(user); / / use mapstruct UserVO1 UserVO1 = UserCovertBasic. INSTANCE. ToConvertVO1 (user); objectList.add("userVO1:" + UserCovertBasic.INSTANCE.toConvertVO1(user)); ObjectList. Add (" userVO1 back to entity class user: "+ UserCovertBasic. INSTANCE. FromConvertEntity1 (userVO1)); / / output conversion results objectList. Add (" userVO2: "+" | "+ UserCovertBasic. INSTANCE. ToConvertVO2 (user)); // use BeanUtils UserVO2 userVO22 = new UserVO2(); BeanUtils.copyProperties(user, userVO22); objectList.add("userVO22:" + " | " + userVO22); return objectList; }}Copy the code

2.4. View the compilation result

UserCovertBasicImpl is an implementation class that automatically generates UserCovertBasic after compilation. The content is as follows:

@Component public class UserCovertBasicImpl implements UserCovertBasic { public UserCovertBasicImpl() { } public UserVO1  toConvertVO1(User source) { if (source == null) { return null; } else { UserVO1 userVO1 = new UserVO1(); userVO1.setId(source.getId()); userVO1.setName(source.getName()); userVO1.setCreateTime(source.getCreateTime()); userVO1.setUpdateTime(source.getUpdateTime()); return userVO1; } } public User fromConvertEntity1(UserVO1 userVO1) { if (userVO1 == null) { return null; } else { User user = new User(); user.setId(userVO1.getId()); user.setName(userVO1.getName()); user.setCreateTime(userVO1.getCreateTime()); user.setUpdateTime(userVO1.getUpdateTime()); return user; } } public UserVO2 toConvertVO2(User source) { if (source == null) { return null; } else { UserVO2 userVO2 = new UserVO2(); userVO2.setId(source.getId()); userVO2.setName(source.getName()); userVO2.setCreateTime(source.getCreateTime()); return userVO2; }}}Copy the code

2.5. View results in the browser

Well, a process is finished, is it simple to feel thief?

To convert a collection, just replace the entity class here with a collection, for example:

    List<UserVO1> toConvertVOList(List<User> source);
Copy the code

Three, not simple situation

We will not show all the code in the following situation. After all, space is limited, so we will directly go to the key code (because the non-key is the same as the above content, ha ha).

3.1. Inconsistent types

We’ll stick with User for the entity class; The mapped object UserVO3 is changed to:

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserVO3 {
    private String id;
    private String name;
    // 实体类该属性是String
    private LocalDateTime createTime;
    // 实体类该属性是LocalDateTime
    private String updateTime;
}
Copy the code

The interface we define needs to be slightly modified:

@Mappings({
            @Mapping(target = "createTime", expression = "java(com.java.mmzsblog.util.DateTransform.strToDate(source.getCreateTime()))"),
    })
    UserVO3 toConvertVO3(User source);

    User fromConvertEntity3(UserVO3 userVO3);
Copy the code

Expression specifies the following expression:

public class DateTransform { public static LocalDateTime strToDate(String str){ DateTimeFormatter df = DateTimeFormatter.ofPattern("yyy-MM-dd HH:mm:ss"); return LocalDateTime.parse("2018-01-12 17:07:05",df); }}Copy the code

Looking at the compiled implementation class through the IDE’s decompilation function, it looks like this:

The expression defined in expression is used to convert the target field createTime at compile time. Then you’ll also see that the updateTime field is automatically converted from LocalDateTime to String.

Summary:

When field types are inconsistent, mapstruct automatically converts the following types:

  • 1. Basic types and their corresponding packaging types.

    At this timemapstructWill automatically unpack. No human manipulation is required
  • 2. Between the wrapper type of the base type and the string type

Other types of conversions can be specified by defining expressions.

3.2 Inconsistent field names

We’ll stick with User for the entity class; The mapped object UserVO4 is changed to:

@data @noargsconstructor @allargsconstructor @constructor public class UserVO4 {// Private String userId; // The property name of the entity class is name private String userName; private String createTime; private String updateTime; }Copy the code

The interface we define needs to be slightly modified:

   @Mappings({
            @Mapping(source = "id", target = "userId"),
            @Mapping(source = "name", target = "userName")
    })
    UserVO4 toConvertVO(User source);

    User fromConvertEntity(UserVO4 userVO4);
Copy the code

Using the IDE’s decompilation function to view the compiled implementation class, the compiled result looks like this:

Obviously, mapstruct does a pretty good job of reading our configured field names and assigning them to their respective positions, but that’s just fine.

Miao Summary:

If the field names are inconsistent, specify the mapping relationship using the @mappings annotation. Then, the corresponding field values can be assigned after compilation.

3.3. Properties are enumerated types

For entity classes we’ll use UserEnum instead:

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserEnum {
    private Integer id;
    private String name;
    private UserTypeEnum userTypeEnum;
}
Copy the code

The mapped object UserVO5 is changed to:

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserVO5 {
    private Integer id;
    private String name;
    private String type;
}
Copy the code

Enumeration objects are:

@getter @allargsconstructor Public enum UserTypeEnum {Java("000", DB("001"), LINUX("002", "Linux operator "); private String value; private String title; }Copy the code

The interface we define is still defined, and does not change if it is an enumeration:

 @Mapping(source = "userTypeEnum", target = "type")
    UserVO5 toConvertVO5(UserEnum source);

    UserEnum fromConvertEntity5(UserVO5 userVO5);
Copy the code

Using the IDE’s decompilation function to view the compiled implementation class, the compiled result looks like this:

Obviously, Mapstruct helps us by enumerating the contents of the type, converting the enumeration to a string, and assigning a value to type. Looks like she’s not just good, she’s thoughtful…