MapStruct(entity优雅的转dto)

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

自己写代码手动转换(这种方式代码量多,而且不优雅)

BeanUtils.copyProperties(entity, dto),这种方式是通过反射来实现的,一般反射效率相对有点低

使用ModelMapper框架(这种方式是通过反射来实现的,一般反射效率相对有点低)

使用MapStruct框架(这种方式是在编译器自动生成转换代码,将原来的手动改为自动,相对于使用反射实现的此种方式效率更好)

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

MapStruct官网地址: http://mapstruct.org/,

GitHub示例程序:https://github.com/mapstruct/mapstruct-examples

1. 添加依赖 和 配置插件

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

<code>Error:(12, 5) java: No property named "xxx" exists in source parameter(s). Did you mean "null"? /<code>

<code> UTF-8 1.8 1.8 1.4.1.Final 1.18.12 org.mapstruct mapstruct ${org.mapstruct.version} org.projectlombok lombok ${org.projectlombok.version} provided org.apache.commons commons-lang3 org.apache.maven.plugins maven-compiler-plugin 3.8.1 1.8 1.8 org.mapstruct mapstruct-processor ${org.mapstruct.version} org.projectlombok lombok ${org.projectlombok.version} /<code>

2. 实体

<code>@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; } } /<code>

<code>@Data public class UserInfo { private String address; private String remark; } /<code>

<code>@NoArgsConstructor @AllArgsConstructor @Data public class User { private Long id; private String name; private String password; private UserStatusEnum userStatusEnum; private Date createTime; private UserInfo userInfo; } /<code>

3. DTO

<code>@NoArgsConstructor @AllArgsConstructor @Data public class UserDTO { private Long id; private String realName; private Integer status; private String address; private String password; private String createTimeFormat; } /<code>

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

<code>@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); } /<code>@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 下生成对应的实现类。

5. Test

<code>@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); } } /<code>