本文原文連接地址:http://nullpointer.pw/mapstruct%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5.htmlhtml
按照平常開發習慣,對於不一樣領域層使用不一樣JavaBean對象傳輸數據,避免相互影響,所以基於數據庫實體對象User衍生出好比UserDto、UserVo等對象,因而在不一樣層之間進行數據傳輸時,不可避免地須要將這些對象進行互相轉換操做。java
常見的轉換方式有:git
第一種方式沒必要說,屬性多了就須要寫一大坨getter/setter代碼。第二種方式比第一種方式要簡便不少,可是坑巨多,好比sources與target寫反,難以定位某個字段在哪裏進行的賦值,同時由於用到反射,致使性能也不佳。github
鑑於此,今天寫一寫第三種對象轉換方式,本文使用的是 MapStruct 工具進行轉換,MapStruct 原理也很簡單,就是在代碼編譯階段生成對應的賦值代碼,底層原理仍是調用getter/setter方法,可是這是由工具替咱們完成,MapStruct在不影響性能的狀況下,解決了前面兩種方式弊端,很贊~spring
爲了講解 MapStruct 工具的使用,本文使用常見的 User 類以及對應 UserDto 對象來演示。數據庫
@Data @Accessors(chain = true) public class User { private Long id; private String username; private String password; // 密碼 private Integer sex; // 性別 private LocalDate birthday; // 生日 private LocalDateTime createTime; // 建立時間 private String config; // 其餘擴展信息,以JSON格式存儲 private String test; // 測試字段 } @Data @Accessors(chain = true) public class UserVo { private Long id; private String username; private String password; private Integer gender; private LocalDate birthday; private String createTime; private List<UserConfig> config; private String test; // 測試字段 @Data public static class UserConfig { private String field1; private Integer field2; } }
注意觀察這兩個類的區別。app
項目中引入 MapStruct 的依賴ide
<dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>1.3.1.Final</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.3.1.Final</version> </dependency>
由於項目中的對象轉換操做基本都同樣,所以抽取除了一個轉換基類,不一樣對象若是隻是簡單轉換能夠直接繼承該基類,而無需覆寫基類任何方法,即只須要一個空類便可。若是子類覆寫了基類的方法,則基類上的 @Mapping 會失效。工具
@MapperConfig public interface BaseMapping<SOURCE, TARGET> { /** * 映射同名屬性 */ @Mapping(target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss") TARGET sourceToTarget(SOURCE var1); /** * 反向,映射同名屬性 */ @InheritInverseConfiguration(name = "sourceToTarget") SOURCE targetToSource(TARGET var1); /** * 映射同名屬性,集合形式 */ @InheritConfiguration(name = "sourceToTarget") List<TARGET> sourceToTarget(List<SOURCE> var1); /** * 反向,映射同名屬性,集合形式 */ @InheritConfiguration(name = "targetToSource") List<SOURCE> targetToSource(List<TARGET> var1); /** * 映射同名屬性,集合流形式 */ List<TARGET> sourceToTarget(Stream<SOURCE> stream); /** * 反向,映射同名屬性,集合流形式 */ List<SOURCE> targetToSource(Stream<TARGET> stream); }
實現 User 與 UserVo 對象的轉換器性能
import org.mapstruct.Mapper; import org.mapstruct.Mapping; @Mapper(componentModel = "spring") public interface UserMapping extends BaseMapping<User, UserVo> { @Mapping(target = "gender", source = "sex") @Mapping(target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss") @Override UserVo sourceToTarget(User var1); @Mapping(target = "sex", source = "gender") @Mapping(target = "password", ignore = true) @Mapping(target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss") @Override User targetToSource(UserVo var1); default List<UserConfig> strConfigToListUserConfig(String config) { return JSON.parseArray(config, UserConfig.class); } default String listUserConfigToStrConfig(List<UserConfig> list) { return JSON.toJSONString(list); } }
本文示例使用的是 Spring 的方式,@Mapper 註解的 componentModel 屬性值爲 spring,不過應該大多數都用的此模式進行開發。
@Mapping用於配置對象的映射關係,示例中 User 對象性別屬性名爲 sex,而UserVo對象性別屬性名爲gender,所以須要配置 target 與 source 屬性。
password 字段不該該返回到前臺,能夠採起兩種方式不進行轉換,第一種就是在vo對象中不出現password字段,第二種就是在@Mapping中設置該字段 ignore = true。
MapStruct 提供了時間格式化的屬性 dataFormat,支持Date、LocalDate、LocalDateTime等時間類型與String的轉換。示例中birthday 屬性爲 LocalDate 類型,能夠無需指定dataFormat自動完成轉換,而LocalDateTime類型默認使用的是ISO格式時間,在國內每每不符合需求,所以須要手動指定一下 dataFormat。
通常經常使用的類型字段轉換 MapStruct都能替咱們完成,可是有一些是咱們自定義的對象類型,MapStruct就不能進行字段轉換,這就須要咱們編寫對應的類型轉換方法,筆者使用的是JDK8,支持接口中的默認方法,能夠直接在轉換器中添加自定義類型轉換方法。
示例中User對象的config屬性是一個JSON字符串,UserVo對象中是List
default List<UserConfig> strConfigToListUserConfig(String config) { return JSON.parseArray(config, UserConfig.class); } default String listUserConfigToStrConfig(List<UserConfig> list) { return JSON.toJSONString(list); }
若是是 JDK8如下的,不支持默認方法,能夠另外定義一個 轉換器,而後再當前轉換器的 @Mapper 中經過 uses = XXX.class 進行引用。
定義好方法以後,MapStruct當匹配到合適類型的字段時,會調用咱們自定義的轉換方法進行轉換。
@Slf4j @RunWith(SpringRunner.class) @SpringBootTest public class MapStructTest { @Resource private UserMapping userMapping; @Test public void tetDomain2DTO() { User user = new User() .setId(1L) .setUsername("zhangsan") .setSex(1) .setPassword("abc123") .setCreateTime(LocalDateTime.now()) .setBirthday(LocalDate.of(1999, 9, 27)) .setConfig("[{\"field1\":\"Test Field1\",\"field2\":500}]"); UserVo userVo = userMapping.sourceToTarget(user); log.info("User: {}", user); // User: User(id=1, username=zhangsan, password=abc123, sex=1, birthday=1999-09-27, createTime=2020-01-17T17:46:20.316, config=[{"field1":"Test Field1","field2":500}]) log.info("UserVo: {}", userVo); // UserVo: UserVo(id=1, username=zhangsan, gender=1, birthday=1999-09-27, createTime=2020-01-17 17:46:20, config=[UserVo.UserConfig(field1=Test Field1, field2=500)]) } @Test public void testDTO2Domain() { UserConfig userConfig = new UserConfig(); userConfig.setField1("Test Field1"); userConfig.setField2(500); UserVo userVo = new UserVo() .setId(1L) .setUsername("zhangsan") .setGender(2) .setCreateTime("2020-01-18 15:32:54") .setBirthday(LocalDate.of(1999, 9, 27)) .setConfig(Collections.singletonList(userConfig)); User user = userMapping.targetToSource(userVo); log.info("UserVo: {}", userVo); // UserVo: UserVo(id=1, username=zhangsan, gender=2, birthday=1999-09-27, createTime=2020-01-18 15:32:54, config=[UserVo.UserConfig(field1=Test Field1, field2=500)]) log.info("User: {}", user); // User: User(id=1, username=zhangsan, password=null, sex=2, birthday=1999-09-27, createTime=2020-01-18T15:32:54, config=[{"field1":"Test Field1","field2":500}]) }
本文涉及代碼已上傳到 Github,以供參考。