mapstruct使用

Mapstruct 版本1.5.0.Beta1
官方文檔
案例-github

前言

MapStruct是一個Java注釋處理器,用于生成類型安全的bean映射類。

您要做的就是定義一個映射器接口,該接口聲明任何必需的映射方法。在編譯期間,MapStruct將生成此接口的實現(xiàn)。此實現(xiàn)使用簡單的Java方法調(diào)用在源對象和目標對象之間進行映射,即沒有反射或類似內(nèi)容。

與手動編寫映射代碼相比,MapStruct通過生成繁瑣且易于出錯的代碼來節(jié)省時間。遵循配置方法上的約定,MapStruct使用合理的默認值,但在配置或?qū)崿F(xiàn)特殊行為時不加理會。
與動態(tài)映射框架相比,MapStruct具有以下優(yōu)點:

  1. 通過使用普通方法調(diào)用(settter/getter)而不是反射來快速執(zhí)行
  2. 編譯時類型安全性:只能映射相互映射的對象和屬性,不能將order實體意外映射到customer DTO等。
  3. 如果有如下問題,編譯時會拋出異常
    3.1 映射不完整(并非所有目標屬性都被映射)
    3.2 映射不正確(找不到正確的映射方法或類型轉(zhuǎn)換)
  4. 可以通過freemarker定制化開發(fā)

1. 設置

MapStruct是基于JSR 269的Java注釋處理器,因此可以在命令行構(gòu)建(javac,Ant,Maven等)以及您的IDE中使用。

它包含以下工件:

  1. org.mapstruct:mapstruct:包含必需的注釋,例如@Mapping
  2. org.mapstruct:mapstruct-processor:包含注釋處理器,該注釋處理器生成映射器實現(xiàn)

1.1 Maven

對于基于Maven的項目,將以下內(nèi)容添加到您的POM文件中以使用MapStruct:

<!--mapStruct依賴 高性能對象映射-->
            <!--mapstruct核心-->
            <dependency>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct</artifactId>
                <version>1.5.0.Beta1</version>
            </dependency>
            <!--mapstruct編譯-->
            <dependency>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct-processor</artifactId>
                <version>1.5.0.Beta1</version>
            </dependency>

Lombok依賴:(版本最好在1.16.16以上,否則會出現(xiàn)問題)通常是和lombok一起使用

<dependency>
            <groupid>org.projectlombok</groupid>
            <artifactid>lombok</artifactid>
            <version>${lombok.version}</version>
            // 版本號 1.18.12
        </dependency>

下載插件(不是必須的,但是挺好用)
idea中下載 mapstruct support 插件,安裝重啟Idea:
[圖片上傳失敗...(image-4f38c1-1652786686690)]

在參數(shù)上,按 ctrl + 鼠標左鍵 ,能夠自動進入?yún)?shù)所在類文件
[圖片上傳失敗...(image-a9937a-1652786686690)]

2. 定義一個映射器

2.1 基本映射

要創(chuàng)建映射器,只需使用所需的映射方法定義一個Java接口,并用注釋對其進行org.mapstruct.Mapper注釋:
該@Mapper注釋將使得MapStruct代碼生成器創(chuàng)建的執(zhí)行PersonMapper 過程中生成時的界面。

在生成的方法實現(xiàn)中,源類型(例如Person)的所有可讀屬性都將被復制到目標類型(例如PersonDTO)的相應屬性中:

  1. 當一個屬性與其目標實體對應的名稱相同時,它將被隱式映射。

  2. 當屬性在目標實體中具有不同的名稱時,可以通過@Mapping注釋指定其名稱。

<mark style="box-sizing: border-box; outline: 0px; background-color: rgb(248, 248, 64); color: rgb(0, 0, 0); overflow-wrap: break-word;">如果不指定@Mapping,默認映射name相同的field
如果映射的對象field name不一樣,通過 @Mapping 指定。
忽略字段加@Mapping#ignore() = true</mark>

@Data
public class Person {

    String describe;

    private String id;

    private String name;

    private int age;

    private BigDecimal source;

    private double height;

    private Date createTime;

}

@Data
public class PersonDTO {

    String describe;

    private Long id;

    private String personName;

    private String age;

    private String source;

    private String height;

}
// mapper
@Mapper
public interface PersonMapper {

    PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);

    @Mapping(target = "name", source = "personName")
    @Mapping(target = "id", ignore = true) // 忽略id,不進行映射
    PersonDTO conver(Person person);

}

生成的實現(xiàn)類:

 public class PersonMapperImpl implements PersonMapper {
    public PersonMapperImpl() {
    }

    public PersonDTO conver(Person person) {
        if (person == null) {
            return null;
        } else {
            PersonDTO personDTO = new PersonDTO();
            personDTO.setDescribe(person.getDescribe());
            if (person.getId() != null) {
                personDTO.setId(Long.parseLong(person.getId()));
            }

            personDTO.setPersonName(person.getName());
            personDTO.setAge(String.valueOf(person.getAge()));
            if (person.getSource() != null) {
                personDTO.setSource(person.getSource().toString());
            }

            personDTO.setHeight(String.valueOf(person.getHeight()));
            return personDTO;
        }
    }
}

測試:

@Test
public void test(){
     Person person = new Person();
     person.setDescribe("測試");
     person.setAge(18);
     person.setName("張三");
     person.setHeight(170.5);
     person.setSource(new BigDecimal("100"));

     PersonDTO dto = PersonMapper.INSTANCT.conver(person);

     System.out.println(dto);
    // PersonDTO(describe=測試, id=null, personName=張三, age=18, source=100, height=170.5)
}

2.2 指定默認值

在@Mapper接口類里面的轉(zhuǎn)換方法上添加@Mapping注解
target() 必須添加,source()可以不添加,則直接使用defaultValue

@Mapping(target = "describe", source = "describe", defaultValue = "默認值")
PersonDTO conver(Person person);

生成的impl:

...
if (person.getDescribe() != null) {
    personDTO.setDescribe(person.getDescribe());
 } else {
     personDTO.setDescribe("默認值");
 }
 ...

測試:


@Test
public void test(){
    Person person = new Person();
    //person.setDescribe("測試");
    person.setAge(18);
    person.setName("張三");
    person.setHeight(170.5);
    person.setSource(new BigDecimal("100"));

    PersonDTO dto = PersonMapper.INSTANCT.conver(person);
    System.out.println(dto);
   // PersonDTO(describe=默認值, id=null, name=張三, age=18, source=100, height=170.5)
}

2.3 使用表達式

目前java是唯一受支持的語言,達式必須以Java表達式的形式給出
<mark style="box-sizing: border-box; outline: 0px; background-color: rgb(248, 248, 64); color: rgb(0, 0, 0); overflow-wrap: break-word;">注意: 這個屬性不能與source()、defaultValue()、defaultExpression()、qualifiedBy()、qualifiedByName()或constant()一起使用。</mark>

@Mapping(target = "describe", source = "describe", defaultValue = "默認值")
@Mapping(target = "createTime",expression = "java(new java.util.Date())")
PersonDTO conver(Person person);

測試:

@Test
public void test(){
     Person person = new Person();
     //person.setDescribe("測試");
     person.setAge(18);
     person.setName("張三");
     person.setHeight(170.5);
     person.setSource(new BigDecimal("100"));

     PersonDTO dto = PersonMapper.INSTANCT.conver(person);

     System.out.println(dto);
    // PersonDTO(describe=默認值, id=null, name=張三, age=18, source=100, height=170.5, createTime=Fri Dec 11 23:21:31 GMT+08:00 2020)

 }

<mark style="box-sizing: border-box; outline: 0px; background-color: rgb(248, 248, 64); color: rgb(0, 0, 0); overflow-wrap: break-word;">默認表達式@Mapping#defaultExpression()是默認值和表達式的組合。僅當source屬性為null時才使用它們</mark>

2.4 dateFormat()

如果屬性從字符串映射到日期,則該格式字符串可由SimpleDateFormat處理,反之亦然。當映射枚舉常量時,將忽略所有其他屬性類型。

....
@Mapping(target = "createTime" ,source = "createTime", dateFormat = "yyyy-MM-dd")
PersonDTO conver(Person person);
...

impl:

try {
    if (person.getCreateTime() != null) {
        personDTO.setCreateTime((new SimpleDateFormat("yyyy-MM-dd")).parse(person.getCreateTime()));
    }
} catch (ParseException var4) {
    throw new RuntimeException(var4);
}

2.5 組合映射

2.5.1 多個源對象

@Data
public class BasicEntity {

    private Date createTime;

    private String createBy;

    private Date updateTime;

    private String updateBy;

    private int _ROW;

}

@Mapper(uses =DateFormtUtil.class)
public interface PersonMapper {

    PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);

    @Mapping(target = "personName",source = "name")
    PersonDTO conver(Person person);

    @Mapping(target = "createTime",source = "basicEntity.createTime")
    PersonDTO combinationConver(Person personC, BasicEntity basicEntity);

}

2.5.2 使用其他的值

...
@Mapping(target = "id", source = "id")
PersonDTO mapTo(Person person, String id);
...

雖然Person和Person有相同的id字段,但是映射器會使用mapTo方法里面的id參數(shù)。

2.6 嵌套映射

@Data
public class Person {
    ...
    private Child personChild;
    ...
}
@Data
public class PersonDTO {
    ...
    private Child child;
    ...
}
// mapper
@Mapper(uses =DateFormtUtil.class)
public interface PersonMapper {

    PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);

    @Mapping(target = "child", source = "personChild")
    PersonDTO conver(Person person);

}

<mark style="box-sizing: border-box; outline: 0px; background-color: rgb(248, 248, 64); color: rgb(0, 0, 0); overflow-wrap: break-word;">如果field name一樣則不需要指定@Mapping</mark>

2.7 numberFormat()

如果帶注釋的方法從數(shù)字映射到字符串,則使用DecimalFormat將格式字符串作為可處理的格式。反之亦然。對于所有其他元素類型,將被忽略。
從基本2.1 基本映射可以看出,number類型與字符串直接的轉(zhuǎn)換是通過valueOf(),如果字符串格式不正確會拋出java.lang.NumberFormatException異常,例如:Integer.valueOf(“10.2”)

使用numberFormat()之后DecimalFormat格式轉(zhuǎn)換,還是會拋出NFE異常

// mapper
....
@Mapping(target = "age",source = "age", numberFormat = "#0.00")
PersonDTO conver(Person person);
...
// imppl
personDTO.setAge((new DecimalFormat("#0.00")).format((long)person.getAge()));

2.8 逆映射

在雙向映射的情況下,例如從實體到DTO以及從DTO到實體,前向方法和反向方法的映射規(guī)則通常是相似的,并且可以通過切換source和來簡單地反轉(zhuǎn)target。

使用注釋@InheritInverseConfiguration表示方法應繼承相應反向方法的反向配置

....
@Mapping(target = "age",source = "age", numberFormat = "#0.00")
PersonDTO conver(Person person);

@InheritInverseConfiguration
Person conver(PersonDTO dto);
...

2.9 繼承與共享配置

2.9.1 繼承配置

方法級配置注解,例如@Mapping,@BeanMapping,@IterableMapping,等等,都可以繼承從一個映射方法的類似使用注釋方法@InheritConfiguration

@Mapper 
public interface CarMapper {     
    @Mapping(target = "numberOfSeats", source = "seatCount")    
    Car carDtoToCar(CarDto car);     

    @InheritConfiguration    
    void carDtoIntoCar(CarDto carDto, @MappingTarget Car car); 
}

上面的示例聲明了一種carDtoToCar()具有配置的映射方法,該配置定義了應如何映射numberOfSeats類型中的屬性Car。在現(xiàn)有Instance實例上執(zhí)行映射的update方法Car需要相同的配置才能成功映射所有屬性。通過聲明@InheritConfiguration該方法,MapStruct可以搜索繼承候選,以應用繼承自該方法的注釋。

如果所有類型的A(源類型和結(jié)果類型)都可以分配給B的相應類型,則一個方法A可以從另一種方法B繼承配置。
如果可以使用多個方法作為繼承的源,則必須在注釋中指定方法名稱:<mark style="box-sizing: border-box; outline: 0px; background-color: rgb(248, 248, 64); color: rgb(0, 0, 0); overflow-wrap: break-word;">@InheritConfiguration( name = “carDtoToCar” )。</mark>

一種方法,可以使用==@InheritConfiguration==和覆蓋或通過另外施加修改的配置@Mapping,@BeanMapping等等。

2.9.2 共享配置

MapStruct提供了通過指向帶注釋的中央接口來定義共享配置的可能性@MapperConfig。為了使映射器使用共享配置,需要在@Mapper#config屬性中定義配置接口。

@MapperConfig注釋具有相同的屬性@Mapper注釋。任何未通過via指定的屬性@Mapper都將從共享配置中繼承。中指定@Mapper的屬性優(yōu)先于通過引用的配置類指定的屬性。列表屬性例如uses可以簡單組合:

@MapperConfig(unmappedTargetPolicy = ReportingPolicy.ERROR ) 
public interface CentralConfig { 
}

@Mapper(config = CentralConfig.class } ) 
// Effective configuration: 
// @Mapper(uses = { CustomMapperViaMapper.class, CustomMapperViaMapperConfig.class },
//     unmappedTargetPolicy = ReportingPolicy.ERROR // ) 
public interface SourceTargetMapper {  ... } 

共享配置config,配置一些檢查策略
例如:

  1. unmappedSourcePolicy()、unmappedTargetPolicy() : 源或者目標沒有標注映射的屬性怎么報告
  2. typeConversionPolicy() :應該報告如何進行有損(縮小)轉(zhuǎn)換,例如:long到integer的轉(zhuǎn)換。
  3. collectionMappingStrategy(): 集合類型映射策略
    其他的,請閱讀源碼

3. 使用自定義方法

3.1 自定義類型轉(zhuǎn)換方法

public class DateMapper {

    public String asString(Date date) {
        return date != null ? new SimpleDateFormat( "yyyy-MM-dd" )
            .format( date ) : null;
    }

    public Date asDate(String date) {
        try {
            return date != null ? new SimpleDateFormat( "yyyy-MM-dd" )
                .parse( date ) : null;
        }
        catch ( ParseException e ) {
            throw new RuntimeException( e );
        }
    }
}

mapper:

@Mapper(uses=DateMapper.class)
public interface PersonMapper{
  PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);
  PersonDTO conver(Person person);
}

impl:

public class PersonMapperImpl implements PersonMapper {
    private final DateMapper dateMapper = new DateMapper();

    public PersonMapperImpl() {
    }

    public PersonDTO conver(Person person) {
      ....
      personDTO.setCreateTime(this.dateMapper.asDate(person.getCreateTime()));
      ...
      return personDTO;

    }
}

在進行類型轉(zhuǎn)換的時候直接調(diào)用改轉(zhuǎn)換方法
@Mapper#uses可以使用多個類

3.2 使用@Qualifier

@Qualifier標記的自定義注解標記的方法,必須有輸入, 否則編譯時會拋出異常

public class DateFormtUtil {

    @DateFormat
    public static String dateToString(Date date){
        return date == null ? "": new SimpleDateFormat("yyyy-MM-dd").format(date);
    }

    @Qualifier
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.CLASS)
    public @interface DateFormat{}
}

mapper:

@Mapper(uses =DateFormtUtil.class)
public interface PersonMapper {

    PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);

    @Mapping(target = "createTime",source = "createTime",qualifiedBy = DateFormat.class)
    PersonDTO conver(Person person);

}

3.3 使用@namd

public class DateFormtUtil {

    @Named("dateToString")
    public static String dateToString(Date date){
        return date == null ? "": new SimpleDateFormat("yyyy-MM-dd").format(date);
    }
}

mapper:

@Mapper(uses =DateFormtUtil.class)
public interface PersonMapper {

    PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);

   @Mapping(target = "createTime",source = "createTime",qualifiedByName = "dateToString")
    PersonDTO conver(Person person);
}

效果跟@Qualifier是一樣的

4.集合映射

MapStructCollectionMappingStrategy,與可能的值:ACCESSOR_ONLY,SETTER_PREFERRED,ADDER_PREFERRED和TARGET_IMMUTABLE。

在下表中,破折號-表示屬性名稱。接下來,尾部s表示復數(shù)形式。該表解釋了這些選項以及它們是如何施加到存在/不存在的set-s,add-s和/或get-s在目標對象上的方法:

選項 僅目標set-s可用 僅目標add-可用 既可以set-s/add- 沒有set-s/add- 現(xiàn)有目標(@TargetType)
ACCESSOR_ONLY set-s get-s set-s get-s get-s
SETTER_PREFERRED set-s add- set-s get-s get-s
ADDER_PREFERRED set-s add- add- get-s get-s
TARGET_IMMUTABLE set-s exception set-s exception set-s

5.集成到 spring

@Mapper#componentModel 中指定依賴注入框架

@Mapper(componentModel = "spring")
public interface ModelMapper {

    ModelMapper INSTANT = Mappers.getMapper(ModelMapper.class);

    ModelVO conver(Model model);

}
// 直接在類中使用Autowired注入就行了
@RestController
class MapperSpringController {

    @Autowired
    ModelMapper modelMapper;

    @GetMapping("/get")
    ModelVO getModle(){
       Model model = new Model();
       model.setId("123456");
       model.setName("張三");
       model.setCreate(new Date());
       return modelMapper.conver(model);
    }
}

6 高級運用

6.1 spi的運用

官方文檔 關(guān)于spi的運用描述

6.2 freemarker生成代碼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • 背景 在代碼開發(fā)中,我們通常都會使用分層架構(gòu),在分層架構(gòu)中都會使用模型轉(zhuǎn)換,在不同的層使用不同的模型。以 DDD ...
    原水寒閱讀 13,757評論 1 6
  • 背景 在一個成熟可維護的工程中,細分模塊后,domian工程最好不要被其他工程依賴,但是實體類一般存于domain...
    jackcooper閱讀 24,707評論 0 7
  • 1.對象屬性映射的苦惱 在日常開發(fā)中,常常涉及到接收Request對象,屬性映射到內(nèi)部交互的VO對象、甚至需要進一...
    西瓜雪梨桔子汁閱讀 3,421評論 0 0
  • 看下使用 之前遇到javaBean之間的轉(zhuǎn)換,可能一般都用BeanUtil.copyProperties直接轉(zhuǎn)換了...
    wang_cheng閱讀 2,715評論 0 0
  • 為了符合“高內(nèi)聚,低耦合”思想,現(xiàn)在主流使用三層架構(gòu)模式,即:表示層(UI)、業(yè)務邏輯層(BLL)和[數(shù)據(jù)訪問層,...
    Neil_Wong閱讀 1,214評論 0 0

友情鏈接更多精彩內(nèi)容