<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          推薦一款Java對象映射神器,別再傻傻手動轉(zhuǎn)換了!

          共 7027字,需瀏覽 15分鐘

           ·

          2020-11-13 16:30

          原文地址:https://nullpointer.pw/mapstruct最佳實踐.html

          前幾天發(fā)的《一份熱乎的 SpringBoot 前后端分離后臺管理系統(tǒng)分析!分模塊開發(fā)、RBAC 權限控制...》這篇文章中我推薦了 MapStruct 來做對象映射。這篇文章就帶著小伙伴們詳細的看一下這個好用的工具庫。

          前言

          按照日常開發(fā)習慣,對于不同領域?qū)邮褂貌煌?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(150, 84, 181);">JavaBean對象傳輸數(shù)據(jù),避免相互影響,因此基于數(shù)據(jù)庫實體對象User衍生出比如UserDtoUserVo等對象,于是在不同層之間進行數(shù)據(jù)傳輸時,不可避免地需要將這些對象進行互相轉(zhuǎn)換操作。

          常見的轉(zhuǎn)換方式有:

          • 調(diào)用getter/setter方法進行屬性賦值
          • 調(diào)用BeanUtil.copyPropertie進行反射屬性賦值

          第一種方式不必說,屬性多了就需要寫一大坨getter/setter代碼。第二種方式比第一種方式要簡便很多,但是坑巨多,比如 sources 與 target 寫反,難以定位某個字段在哪里進行的賦值,同時因為用到反射,導致性能也不佳。

          鑒于此,今天寫一寫第三種對象轉(zhuǎn)換方式,本文使用的是 MapStruct 工具進行轉(zhuǎn)換,MapStruct 原理也很簡單,就是在代碼編譯階段生成對應的賦值代碼,底層原理還是調(diào)用getter/setter方法,但是這是由工具替我們完成,MapStruct在不影響性能的情況下,解決了前面兩種方式弊端,很贊~

          準備工作

          為了講解 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;?//?創(chuàng)建時間
          ????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?config;
          ???private?String?test;?//?測試字段
          ????@Data
          ????public?static?class?UserConfig?{
          ????????private?String?field1;
          ????????private?Integer?field2;
          ????}
          }

          注意觀察這兩個類的區(qū)別。

          一、MapStruct 配置以及基礎使用

          項目中引入 MapStruct 的依賴

          <dependency>
          ??<groupId>org.mapstructgroupId>
          ??<artifactId>mapstructartifactId>
          ??<version>1.3.1.Finalversion>
          dependency>
          <dependency>
          ??<groupId>org.mapstructgroupId>
          ??<artifactId>mapstruct-processorartifactId>
          ??<version>1.3.1.Finalversion>
          dependency>

          因為項目中的對象轉(zhuǎn)換操作基本都一樣,因此抽取除了一個轉(zhuǎn)換基類,不同對象如果只是簡單轉(zhuǎn)換可以直接繼承該基類,而無需覆寫基類任何方法,即只需要一個空類即可。如果子類覆寫了基類的方法,則基類上的 @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?sourceToTarget(List?var1);

          ????/**
          ?????*?反向,映射同名屬性,集合形式
          ?????*/

          ????@InheritConfiguration(name?=?"targetToSource")
          ????List?targetToSource(List?var1);

          ????/**
          ?????*?映射同名屬性,集合流形式
          ?????*/

          ????List?sourceToTarget(Stream?stream);

          ????/**
          ?????*?反向,映射同名屬性,集合流形式
          ?????*/

          ????List?targetToSource(Stream?stream);
          }

          實現(xiàn) User 與 UserVo 對象的轉(zhuǎn)換器

          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?strConfigToListUserConfig(String?config)?{
          ????????return?JSON.parseArray(config,?UserConfig.class);
          ????}

          ????default?String?listUserConfigToStrConfig(List?list)?{
          ????????return?JSON.toJSONString(list);
          ????}
          }

          本文示例使用的是 Spring 的方式,@Mapper注解的 componentModel 屬性值為 spring,不過應該大多數(shù)都用的此模式進行開發(fā)。

          @Mapping用于配置對象的映射關系,示例中 User 對象性別屬性名為 sex,而UserVo對象性別屬性名為gender,因此需要配置 targetsource 屬性。

          password 字段不應該返回到前臺,可以采取兩種方式不進行轉(zhuǎn)換,第一種就是在 vo 對象中不出現(xiàn) password 字段,第二種就是在@Mapping中設置該字段ignore = true

          MapStruct 提供了時間格式化的屬性 dataFormat,支持DateLocalDateLocalDateTime等時間類型與String的轉(zhuǎn)換。示例中birthday 屬性為 LocalDate 類型,可以無需指定dataFormat自動完成轉(zhuǎn)換,而LocalDateTime類型默認使用的是 ISO 格式時間,在國內(nèi)往往不符合需求,因此需要手動指定一下 dataFormat

          二、自定義屬性類型轉(zhuǎn)換方法

          一般常用的類型字段轉(zhuǎn)換 MapStruct都能替我們完成,但是有一些是我們自定義的對象類型,MapStruct就不能進行字段轉(zhuǎn)換,這就需要我們編寫對應的類型轉(zhuǎn)換方法,筆者使用的是 JDK8,支持接口中的默認方法,可以直接在轉(zhuǎn)換器中添加自定義類型轉(zhuǎn)換方法。

          示例中User對象的 config 屬性是一個 JSON 字符串,UserVo對象中是 List 類型的,這需要實現(xiàn) JSON 字符串與對象的互轉(zhuǎn)。

          default?List?strConfigToListUserConfig(String?config)?{
          ??return?JSON.parseArray(config,?UserConfig.class);
          }

          default?String?listUserConfigToStrConfig(List?list)?{
          ??return?JSON.toJSONString(list);
          }

          如果是 JDK8 以下的,不支持默認方法,可以另外定義一個 轉(zhuǎn)換器,然后再當前轉(zhuǎn)換器的 @Mapper 中通過 uses = XXX.class 進行引用。

          定義好方法之后,MapStruct當匹配到合適類型的字段時,會調(diào)用我們自定義的轉(zhuǎn)換方法進行轉(zhuǎn)換。

          三、單元測試

          @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}])
          ??}

          四、常見問題

          1. 當兩個對象屬性不一致時,比如User對象中某個字段不存在與UserVo當中時,在編譯時會有警告提示,可以在@Mapping中配置 ignore = true,當字段較多時,可以直接在@Mapper中設置unmappedTargetPolicy屬性或者unmappedSourcePolicy屬性為 ReportingPolicy.IGNORE即可。
          2. 如果項目中也同時使用到了 Lombok,一定要注意 Lombok 的版本要等于或者高于1.18.10,否則會有編譯不通過的情況發(fā)生,筆者掉進這個坑很久才爬了出來,希望各位不要重復踩坑。

          代碼下載

          本文涉及代碼已上傳到 Github,以供參考。

          • mapstruct 最佳實踐示例代碼[1]

          參考

          • 官方文檔[2]
          • 官方 FAQ[3]
          • 官方 Examples[4]
          • 機翻中文版文檔[5]
          • 5 種常見 Bean 映射工具的性能比對[6]

          參考資料

          [1]

          mapstruct 最佳實踐示例代碼: https://github.com/Mosiki/learning-modules/tree/master/learning-mapstruct

          [2]

          官方文檔: https://mapstruct.org/documentation/stable/reference/html/

          [3]

          官方 FAQ: https://mapstruct.org/faq/

          [4]

          官方 Examples: https://github.com/mapstruct/mapstruct-examples

          [5]

          機翻中文版文檔: http://www.kailing.pub/MapStruct1.3/index.html

          [6]

          5 種常見 Bean 映射工具的性能比對: https://www.cnblogs.com/javaguide/p/11861749.html

          最近寫的一些干貨,每篇都很用心,歡迎各位小伙伴閱讀/點贊/分享:

          1. Spring Boot搭建的一個在線文件預覽系統(tǒng)!支持ppt、doc等多種類型文件預覽

          2. 系統(tǒng)設計面試題指北

          3. ?大二逃課總結(jié)的1.2w字的計算機網(wǎng)絡知識!掃盲!

          4. ?5分“白嫖”我使用Github 5 年總結(jié)的這些騷操作

          5. ?真香!被阿里老兄安利了一個輕量級的日志追蹤框架。10 分鐘即可接入!

          6. ?一份熱乎的 SpringBoot 前后端分離后臺管理系統(tǒng)分析!分模塊開發(fā)、RBAC權限控制...


          我是Guide哥,Java后端開發(fā),會一點前端知識,喜歡烹飪,自由的少年。一個三觀比主角還正的技術人。我們下期再見!


          瀏覽 38
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  成人网久久精品无码AV | 亚洲色图综合 | 艹艹视频 | 91视频久久久久久久久久久 | 亚洲无码在线免费观看视频吗? |