<kbd id="5sdj3"></kbd>
<th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>

    推薦一款 Java 對(duì)象映射神器

    共 5618字,需瀏覽 12分鐘

     ·

    2020-12-31 00:37

    程序員的成長(zhǎng)之路
    互聯(lián)網(wǎng)/程序員/技術(shù)/資料共享?
    關(guān)注


    閱讀本文大概需要 5 分鐘。

    來(lái)自:網(wǎng)絡(luò)

    前言
    按照日常開(kāi)發(fā)習(xí)慣,對(duì)于不同領(lǐng)域?qū)邮褂貌煌琂avaBean對(duì)象傳輸數(shù)據(jù),避免相互影響,因此基于數(shù)據(jù)庫(kù)實(shí)體對(duì)象User衍生出比如UserDto、UserVo等對(duì)象,于是在不同層之間進(jìn)行數(shù)據(jù)傳輸時(shí),不可避免地需要將這些對(duì)象進(jìn)行互相轉(zhuǎn)換操作。
    常見(jiàn)的轉(zhuǎn)換方式有:
    • 調(diào)用getter/setter方法進(jìn)行屬性賦值
    • 調(diào)用BeanUtil.copyPropertie進(jìn)行反射屬性賦值
    第一種方式不必說(shuō),屬性多了就需要寫(xiě)一大坨getter/setter代碼。第二種方式比第一種方式要簡(jiǎn)便很多,但是坑巨多,比如sources與target寫(xiě)反,難以定位某個(gè)字段在哪里進(jìn)行的賦值,同時(shí)因?yàn)橛玫椒瓷?,?dǎo)致性能也不佳。
    鑒于此,今天寫(xiě)一寫(xiě)第三種對(duì)象轉(zhuǎn)換方式,本文使用的是 MapStruct 工具進(jìn)行轉(zhuǎn)換,MapStruct 原理也很簡(jiǎn)單,就是在代碼編譯階段生成對(duì)應(yīng)的賦值代碼,底層原理還是調(diào)用getter/setter方法,但是這是由工具替我們完成,MapStruct在不影響性能的情況下,解決了前面兩種方式弊端,很贊~

    準(zhǔn)備工作

    為了講解 MapStruct 工具的使用,本文使用常見(jiàn)的 User 類(lèi)以及對(duì)應(yīng) UserDto 對(duì)象來(lái)演示。
    @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;?
    ????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;
    ????}
    }
    注意觀(guān)察這兩個(gè)類(lèi)的區(qū)別。

    一、MapStruct 配置以及基礎(chǔ)使用

    項(xiàng)目中引入 MapStruct 的依賴(lài)
    <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>
    因?yàn)轫?xiàng)目中的對(duì)象轉(zhuǎn)換操作基本都一樣,因此抽取除了一個(gè)轉(zhuǎn)換基類(lèi),不同對(duì)象如果只是簡(jiǎn)單轉(zhuǎn)換可以直接繼承該基類(lèi),而無(wú)需覆寫(xiě)基類(lèi)任何方法,即只需要一個(gè)空類(lèi)即可。如果子類(lèi)覆寫(xiě)了基類(lèi)的方法,則基類(lèi)上的?@Mapping會(huì)失效。
    @MapperConfig
    public interface BaseMapping {

    ????/**
    ?????* 映射同名屬性
    ?????*/

    ????@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<TARGET>?sourceToTarget(Stream<SOURCE>?stream);

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

    ????List<SOURCE>?targetToSource(Stream<TARGET>?stream);
    }
    實(shí)現(xiàn) User 與 UserVo 對(duì)象的轉(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,不過(guò)應(yīng)該大多數(shù)都用的此模式進(jìn)行開(kāi)發(fā)。
    @Mapping用于配置對(duì)象的映射關(guān)系,示例中 User 對(duì)象性別屬性名為 sex,而UserVo對(duì)象性別屬性名為gender,因此需要配置 target 與 source 屬性。
    password 字段不應(yīng)該返回到前臺(tái),可以采取兩種方式不進(jìn)行轉(zhuǎn)換,第一種就是在vo對(duì)象中不出現(xiàn)password字段,第二種就是在@Mapping中設(shè)置該字段 ignore = true。
    MapStruct 提供了時(shí)間格式化的屬性?dataFormat,支持Date、LocalDate、LocalDateTime等時(shí)間類(lèi)型與String的轉(zhuǎn)換。示例中birthday 屬性為 LocalDate 類(lèi)型,可以無(wú)需指定dataFormat自動(dòng)完成轉(zhuǎn)換,而LocalDateTime類(lèi)型默認(rèn)使用的是ISO格式時(shí)間,在國(guó)內(nèi)往往不符合需求,因此需要手動(dòng)指定一下 dataFormat。

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

    一般常用的類(lèi)型字段轉(zhuǎn)換 MapStruct都能替我們完成,但是有一些是我們自定義的對(duì)象類(lèi)型,MapStruct就不能進(jìn)行字段轉(zhuǎn)換,這就需要我們編寫(xiě)對(duì)應(yīng)的類(lèi)型轉(zhuǎn)換方法,筆者使用的是JDK8,支持接口中的默認(rèn)方法,可以直接在轉(zhuǎn)換器中添加自定義類(lèi)型轉(zhuǎn)換方法。
    示例中User對(duì)象的config屬性是一個(gè)JSON字符串,UserVo對(duì)象中是List類(lèi)型的,這需要實(shí)現(xiàn)JSON字符串與對(duì)象的互轉(zhuǎn)。
    default?List strConfigToListUserConfig(String config) {
    ??return?JSON.parseArray(config, UserConfig.class);
    }

    default?String listUserConfigToStrConfig(List?list) {
    ??return?JSON.toJSONString(list);
    }
    如果是 JDK8以下的,不支持默認(rèn)方法,可以另外定義一個(gè) 轉(zhuǎn)換器,然后再當(dāng)前轉(zhuǎn)換器的 @Mapper 中通過(guò) uses = XXX.class 進(jìn)行引用。
    定義好方法之后,MapStruct當(dāng)匹配到合適類(lèi)型的字段時(shí),會(huì)調(diào)用我們自定義的轉(zhuǎn)換方法進(jìn)行轉(zhuǎn)換。

    三、單元測(cè)試


    @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);
    ????
    ????log.info("UserVo: {}", userVo);
    ????
    ??}

    ??@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);
    ????
    ????log.info("User: {}", user);
    ????
    ??}

    四、常見(jiàn)問(wèn)題

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

    代碼下載

    本文涉及代碼已上傳到 Github,以供參考。
    https://github.com/Mosiki/learning-modules/tree/master/learning-mapstruct
    推薦閱讀:
    全球第二大瀏覽器升級(jí)!內(nèi)存/CPU占用暴降
    Java 生成隨機(jī)數(shù)的 5 種方式,你知道幾種?
    5T技術(shù)資源大放送!包括但不限于:C/C++,Linux,Python,Java,PHP,人工智能,單片機(jī),樹(shù)莓派,等等。在公眾號(hào)內(nèi)回復(fù)「2048」,即可免費(fèi)獲?。?!

    微信掃描二維碼,關(guān)注我的公眾號(hào)

    朕已閱?

    瀏覽 58
    點(diǎn)贊
    評(píng)論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報(bào)
    評(píng)論
    圖片
    表情
    推薦
    點(diǎn)贊
    評(píng)論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報(bào)

    <kbd id="5sdj3"></kbd>
    <th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>
    奇米影视7777狠狠狠狠视频 | 看大黄美女全裸大黄 | 狠狠操狠狠操 | 一区二区三区无码翻白眼 | 又黑又长的大黑鸡巴免费高清 |