MapStruct(entity优雅的转dto)


开发中我们可能使用JPA、通用Mapper或者MyBatis-Plus去查询数据,而这类框架都是返回的实体,实体是和数据库中的表是一一对应的,而作为接口的返回值我们无需把整个实体都暴露给前端,通常会将实体转换成另一个DTO对象来返回,通常有以下转换方式:

  • 自己写代码手动转换(这种方式代码量多,而且不优雅)
  • BeanUtils.copyProperties(entity, dto),这种方式是通过反射来实现的,一般反射效率相对有点低
  • 使用ModelMapper框架(这种方式是通过反射来实现的,一般反射效率相对有点低)
  • 使用MapStruct框架(这种方式是在编译器自动生成转换代码,将原来的手动改为自动,相对于使用反射实现的此种方式效率更好)

综合比较性能、问题排查、文档、成熟度、扩展性等因素来考虑,MapStruct 是一个不错的选择,实体映射转换各个工具比较 https://java.libhunt.com/compare-mapstruct-vs-selma

MapStruct(entity优雅的转dto)

1. 添加依赖 和 配置插件

注意:如果使用了lombok应尽量使用比较高的版本,maven-compiler-plugin 插件也最好使用较高的版本。否则有可能报这个错:

Error:(12, 5) java: No property named “xxx” exists in source parameter(s). Did you mean “null”?

maven pom.xml:


<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <org.mapstruct.version>1.4.1.Final</org.mapstruct.version>
    <org.projectlombok.version>1.18.12</org.projectlombok.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>${org.mapstruct.version}</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${org.projectlombok.version}</version>
        <scope>provided</scope>
    </dependency>

    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
    </dependency>
</dependencies>

<build>
    <pluginManagement>
        <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>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>${org.projectlombok.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
    </pluginManagement>
</build>

2. 实体

@Getter
public enum UserStatusEnum {

   NORMAL(0, "正常"),
   LOCK(1, "锁定");


   private Integer code;
   private String desc;


   UserStatusEnum(Integer code, String desc) {
      this.code = code;
      this.desc = desc;
   }
}
@Data
public class UserInfo {
    private String address;
    private String remark;
}
@NoArgsConstructor
@AllArgsConstructor
@Data
public class User {
    private Long id;
    private String name;
    private String password;
    private UserStatusEnum userStatusEnum;
    private Date createTime;

    private UserInfo userInfo;
}

3. DTO

@NoArgsConstructor
@AllArgsConstructor
@Data
public class UserDTO {

    private Long id;
    private String realName;
    private Integer status;
    private String address;
    private String password;

    private String createTimeFormat;
}

4. 实体与DTO属性映射配置

@Mapper(componentModel="spring")
public interface UserConverter {

    @Mappings({
        @Mapping(source = "name", target = "realName"),
        @Mapping(target = "status", expression = "java(user.getUserStatusEnum().getCode())"),
        @Mapping(source = "createTime", target = "createTimeFormat", dateFormat = "yyyy-MM-dd HH:mm:ss"),
        @Mapping(source = "userInfo.address", target = "address"),
        @Mapping(target = "password", ignore = true)
    })
    UserDTO entity2dto(User user);
}
  • @Mapper 只有在接口加上这个注解,MapStruct 才会去实现该接口,@Mapper 里有个 componentModel 属性,主要是指定实现类的类型,一般用到两个:
  • default:默认,可以通过 Mappers.getMapper(Class) 方式获取实例对象。
  • spring:在接口的实现类上自动添加注解 @Component,可通过 @Autowired 方式注入。
  • @Mappings:配置多个@Mapping
  • @Mapping:属性映射,若源对象属性与目标对象名字一致,会自动映射对应属性
  • source:源属性
  • target:目标属性
  • dateFormat:String 到 Date 日期之间相互转换,通过 SimpleDateFormat,该值为 SimpleDateFormat的日期格式
  • expression:使用Java方法来格式化值
  • ignore: 忽略这个字段

@Mapper可以单独放在一个类中配置,也可以在JPA或者MyBatis中的Mapper类中来配置。

手工编译(mvn compile)或者启动 IDE 的时候, 会自动在 target/classes 下生成对应的实现类。

MapStruct(entity优雅的转dto)

5. Test

@SpringBootTest
class SpringbootMapstructApplicationTests {

    @Autowired
    private UserConverter userConverter;


    @Test
    public void testMapStruct() {
        UserInfo userInfo = new UserInfo();
        userInfo.setAddress("上海市");
        userInfo.setRemark("此人非常懒");

        User user = new User();
        user.setId(1L);
        user.setName("周某人");
        user.setPassword("123456");
        user.setUserStatusEnum(UserStatusEnum.LOCK);
        user.setCreateTime(new Date());
        user.setUserInfo(userInfo);

        UserDTO userDTO = userConverter.entity2dto(user);
        System.out.println(userDTO);
    }
}

MapStruct(entity优雅的转dto)


文章作者: Cheney
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Cheney !
 上一篇
String对象的那些事,几行代码就解释得清清楚楚 String对象的那些事,几行代码就解释得清清楚楚
前言String是Java中十分常用的类,在面试题中也是出镜率很高的常客,本文将我自己学习中遇到的一些问题进行整理,如果有误,欢迎指正。 String对象判等千万不要用 == 去判断String对象是否相等,==比较的是地址。JVM只会共享
2020-12-26
下一篇 
开源利器:自动生成随机 mock 数据测试对象 开源利器:自动生成随机 mock 数据测试对象
测试的痛点大家好,我是老马。 每一位开发者大部分工作都是写代码、测试代码、修BUG。 我们有很多测试代码,总是花费大量的实践去构建一个对象。 于是就在想,能不能自动填充一个对象呢? 于是去 github 查了一下,找到了一个测试神器 dat
2020-12-25
  目录