開發中我們可能使用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>