1. 程式人生 > 實用技巧 >屬性對映工具——MapStruct(三)

屬性對映工具——MapStruct(三)

目錄:

屬性對映工具——MapStruct(一)

屬性對映工具——MapStruct(二)

屬性對映工具——MapStruct(三)

屬性對映工具——MapStruct(四)

屬性對映工具——MapStruct(五)


好,我們繼續吧,這是MapStruct系列的第三篇。今天我們講的是MapStruct的高階語法。

一、常用註解解釋

1.1、@Mapper——表示該介面作為對映介面,編譯時MapStruct處理器的入口

  1)uese:外部引入的轉換類;

  2)componentModel:就是依賴注入,類似於在spring的servie層用@servie注入,那麼在其他地方可以使用@Autowired取到值。該屬性可取的值為

    a)預設:這個就是經常使用的 xxxMapper.INSTANCE.xxx;

    b)cdi:使用該屬性,則在其他地方可以使用@Inject取到值;

    c)spring:使用該屬性,則在其他地方可以使用@Autowired取到值;

    d)jsr330/Singleton:使用者兩個屬性,可以再其他地方使用@Inject取到值;

1.2、@Mappings——一組對映關係,值為一個數組,元素為@Mapping

1.3、@Mapping——一對對映關係

  1)target:目標屬性,賦值的過程是把“源屬性”賦值給“目標屬性”;

  2)source:源屬性,賦值的過程是把“源屬性”賦值給“目標屬性”;

  3)dateFormat:用於源屬性是Date,轉化為String;

  4)numberFormat:使用者數值型別與String型別之間的轉化;

  5)constant:不管源屬性,直接將“目標屬性”置為常亮;

  6)expression:使用表示式進行屬性之間的轉化;

  7)ignore:忽略某個屬性的賦值;

  8)qualifiedByName:根據自定義的方法進行賦值;

  9)defaultValue:預設值;

1.4、@MappingTarget——用在方法引數的前面。使用此註解,源物件同時也會作為目標物件,用於更新。

1.5、@InheritConfiguration——指定對映方法

1.6、@InheritInverseConfiguration——表示方法繼承相應的反向方法的反向配置

1.7、@Named——定義類/方法的名稱

二、mapStruct高階語法1

2.0、示例準備

  先準備幾個類。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student1 {
    //主鍵
    private long id;
    //編號
    private String no;
    //名稱
    private String name1;
    //年齡
    private String age1;
    //生日
    private Date birthday1;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student2{
    //主鍵
    private long id;
    //編號
    private String no;
    //名稱
    private String name2;
    //年齡
    private Integer age2;
    //生日
    private String birthday2;
    //地址(市)
    private String addresCity;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Address {
    //
    private String province;
    //
    private String city;
    //
    private String county;
    //街道
    private String street;
}

2.1、使用抽象類代替介面

  之前我們使用的是介面來實現屬性的轉化,編譯過後自動生成該介面的實現類。實際上也可以使用“抽象類”實現屬性的轉化,編譯過後自動生成該抽象類的子類。當然此時抽象類裡面的方法當然是抽象方法了。對映器的形式有所變化,但是大部分是相似的。提到對映器,這裡多說一句:MapStruct生成的對映器是執行緒安全的,因此可以安全地從多個執行緒同時訪問它們。以下的所有例子都是基於“抽象類”的,所以這裡再不舉例了。

2.2、多引數賦值

    這個就是多引數賦值,需要注意的是,此時source的值需要加上引數的字首,用於區分。還有這裡我們使用了抽象類代替了之前的介面。

/**
* Student2(id=0, no=null, name2=張三, age2=null, birthday2=null, addresCity=安慶市)
*/
@Test
public void test1(){
    Student1 student1 = new Student1();
    student1.setName1("張三");
    Address address = new Address("安徽省","安慶市","太湖縣","mapStruct街道");
    Student2 student2 = StudentMapper.INSTANCE.toStudent2(student1,address);
    System.out.println(student2);
}
@Mapper
public abstract class StudentMapper {

    public static final StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);

    @Mappings({
            @Mapping(source = "address.city",target = "addresCity"),
            @Mapping(source = "student1.name1",target = "name2")
    })
    abstract Student2 toStudent2(Student1 student1,Address address);
}

2.3、直接使用引數作為屬性值

/**
* Student2(id=0, no=null, name2=null, age2=null, birthday2=null, addresCity=天津市)
*/
@Test
public void test3(){
    Student1 student1 = new Student1();
    student1.setName1("王五");
    Student2 student2 = StudentMapper.INSTANCE.toStudent2(student1,"天津市");
    System.out.println(student2);
}
@Mapper
public abstract class StudentMapper {

    public static final StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);
@Mappings({ @Mapping(source
= "city",target = "addresCity"), }) abstract Student2 toStudent2(Student1 student1,String city); }

2.4、更新物件屬性

  之前我們的轉化的方法都是有返回值的,比如將A賦值給B,那麼轉化的方法引數是A,返回型別為B。但是現在,我要說,轉化方法可以使無返回值,比如將A賦值給B,採用此方法,方法的引數是A和B,只是需要在引數B前面加 @MappingTarget。類似與對B的更新,如下:

/**
     * 轉化前Student2(id=0, no=null, name2=null, age2=null, birthday2=null, addresCity=北京市)
     * 轉化後Student2(id=0, no=null, name2=王五, age2=null, birthday2=null, addresCity=北京市)
     */
    @Test
    public void test2(){
        Student2 student2 = new Student2();
        student2.setAddresCity("北京市");
        System.out.println("轉化前"+student2);
        Student1 student1 = new Student1();
        student1.setName1("王五");
        StudentMapper.INSTANCE.updateStudent2(student1,student2);
        System.out.println("轉化後"+student2);
    }
@Mapper
public abstract class StudentMapper {

    public static final StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);

    @Mapping(source = "name1",target = "name2")
    abstract void updateStudent2(Student1 student1, @MappingTarget Student2 student2);
}

三、mapStruct高階語法2

3.0、示例準備

@Data
@NoArgsConstructor
@AllArgsConstructor
class Student1 {
    //主鍵
    private Long id;
    //名稱
    private String name1;
    //年齡
    private String age1;
    //建立時間
    private Date createTime1;
    //體重
    private Double weight1;
    //身高
    private String bloodType1;
    //性別
    private SexEnum sex1;
}


@Data
@NoArgsConstructor
@AllArgsConstructor
class Student2 {
    //主鍵
    private Long id;
    //名稱
    private String name2;
    //年齡
    private Integer age2;
    //建立時間
    private String createTime2;
    //體重
    private String weight2;
    //身高
    private String bloodType2;
    //性別(0:女,1:男)
    private Integer sex2;
}

enum SexEnum {
    MAN(1, "男"),
    WOMAN(0, "女");

    @Setter
    @Getter
    private Integer code;
    @Setter
    @Getter
    private String desc;

    SexEnum(Integer code, String desc) {
        this.code = code;
        this.desc = desc;
    }
}
View Code

3.1、對映反轉

    我們首先看這樣一個例子,在實際的專案中,經常是需要將A ——> B,還有就是將B——> A。根據我們目前所學到的mapStruct知識,我們是這樣寫的。以下是一個示例。

    

  不知你注意到了沒,寫的很繁瑣,很累贅。實際上在people1轉化為people2的時候,我規則都配置了(下文稱:原對映);而people2轉people的時候,這些規則又寫了一遍(下文稱:新對映),很明顯原對映與新對映是一對互逆的操作,所以如果程式智慧一點,在原對映存在的前提下,people2轉people1的時候應該我就不用配置了,或者基於原對映稍微修改就好了,這是最理想的狀況。那mapStruct能做到嗎?能,當然能,不然mapStruct怎麼能說強大呢,我們需要使用@InheritInverseConfiguration。它能滿足我們的需求。該註解需要注意三點:

1)有一個註解@InheritConfiguration(下文提到)與該註解長得很像,請注意區分,雖然他們的功能有點相似,但是他們是不同的註解;
2)該註解雖然可以實現反轉賦值,但是有一種情況需要手動加強——原對映的Mapping中沒有source(典型的使用了expression、constant屬性的都屬於)。對於有這種情況的屬性,原對映與新對映都需要指定出來;

3)該註解只有一個屬性name,它的值是原對映名。你可以把它理解為原對映對應的方法的名字,即方法的名字就是對映名。如上面的例子,規則名是"toPeople2"。那該註解可以這樣寫@InheritInverseConfiguration(name="toPeople2")。

  

      下面是我準備的一個例子,有三點需要注意:

1)可以看到由於age的轉化中使用了expression、bloodType轉化的過程中使用了constant。這兩個Mapping都沒有source。所以我們在使用了@InheritInverseConfiguration註解的同時,又使用了@Mapping對這兩個屬性做了單獨的加強,不然會報錯;

2)sex屬性,該屬性是一個列舉,在原對映中我們使用了qualifiedByName = "getBySexEnum"指定了對應的轉化方法,但是新對映中我們沒有指定mapStruct是怎麼知道的呢,我們看生成的實現方法,新對映裡面使用了getByCode。mapStruct怎麼知道要用getByCode的呢——猜的。確實是猜的,它的思想是,在方法裡面找引數、返回值都符合的方法。本例中,我們要將Student2.sex2(Integer型別) 賦值給 Student1.sex(SexEnum型),mapStruct就找引數為Integer,返回值為SexEnum的方法——只有getByCode符合,所以就用該方法轉化了sex屬性。講到這裡,是不是有疑問了,那我定義多個方法:引數相同、返回值相同,就是方法名不同,mapStruct怎麼識別用哪個方法呢。這個要不你就特殊指定,如果不指定,會編譯錯誤!!所以儘量定義的方法具有“唯一性”,使mapStruct能智慧識別。

3)mapping的expression屬性,應該是我第一次用到,它的值就是一個java程式碼,只不過在書寫的時候注意,需要使用java(我是java程式碼)包起來,使用String,Interger之類的儘量使用全路徑。

@Mapper
public interface StudentMapper {
    StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);

    @Mappings({
            @Mapping(source = "name1", target = "name2",defaultValue = "某某人"),
            @Mapping(target = "age2", expression = "java(java.lang.Integer.valueOf(student1.getAge1()))"),
            @Mapping(source = "createTime1", target = "createTime2", dateFormat = "yyyy-MM-dd HH:mm:ss:SSS"),
            @Mapping(source = "weight1", target = "weight2", numberFormat = "0.00"),
            @Mapping(target = "bloodType2", constant = "A 型血"),
            @Mapping(source = "sex1",target = "sex2",qualifiedByName = {"getBySexEnum"})
    })
    Student2 toStudent2(Student1 student1);

    @InheritInverseConfiguration(name = "toStudent2")
    @Mappings({
            @Mapping(target = "age1", expression = "java(java.lang.String.valueOf(student2.getAge2()))"),
            @Mapping(target = "bloodType1", constant = "YY 型血")
    })
    Student1 toStudent1(Student2 student2);

  //==========================================================================================
    @Named("getByCode")
    default SexEnum getByCode(Integer code){
        SexEnum[] sexEnums = SexEnum.values();
        for (SexEnum item:sexEnums){
            if(item.getCode().equals(code)){
                return item;
            }
        }
        return null;
    }

    @Named("getBySexEnum")
    default Integer getBySexEnum(SexEnum sexEnum){
        return sexEnum.getCode();
    }
}

對應的生成的實現類

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2020-07-24T16:19:37+0800",
    comments = "version: 1.2.0.Final, compiler: javac, environment: Java 1.8.0_201 (Oracle Corporation)"
)
public class StudentMapperImpl implements StudentMapper {
    @Override
    public Student2 toStudent2(Student1 student1) {
        if ( student1 == null ) {
            return null;
        }
        Student2 student2 = new Student2();
        student2.setSex2( getBySexEnum( student1.getSex1() ) );
        if ( student1.getName1() != null ) {
            student2.setName2( student1.getName1() );
        }
        else {
            student2.setName2( "某某人" );
        }
        if ( student1.getCreateTime1() != null ) {
            student2.setCreateTime2( new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss:SSS" ).format( student1.getCreateTime1() ) );
        }
        if ( student1.getWeight1() != null ) {
            student2.setWeight2( new DecimalFormat( "0.00" ).format( student1.getWeight1() ) );
        }
        student2.setId( student1.getId() );
        student2.setBloodType2( "A 型血" );
        student2.setAge2( java.lang.Integer.valueOf(student1.getAge1()) );
        return student2;
    }


    @Override
    public Student1 toStudent1(Student2 student2) {
        if ( student2 == null ) {
            return null;
        }
        Student1 student1 = new Student1();
        try {
            if ( student2.getCreateTime2() != null ) {
                student1.setCreateTime1( new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss:SSS" ).parse( student2.getCreateTime2() ) );
            }
        }
        catch ( ParseException e ) {
            throw new RuntimeException( e );
        }
        student1.setName1( student2.getName2() );
        student1.setSex1( getByCode( student2.getSex2() ) );
        try {
            if ( student2.getWeight2() != null ) {
                student1.setWeight1( new DecimalFormat( "0.00" ).parse( student2.getWeight2() ).doubleValue() );
            }
        }
        catch ( ParseException e ) {
            throw new RuntimeException( e );
        }
        student1.setId( student2.getId() );
        student1.setBloodType1( "YY 型血" );
        student1.setAge1( java.lang.String.valueOf(student2.getAge2()) );
        return student1;
    }
}

3.2、使用已有的對映更新物件屬性

    我感覺上面3.1已經敘述的夠詳細了,這裡我就不再累述了。只不過是使用的@InheritConfiguration註解。附上示例程式碼。

@Mapper
public interface StudentMapper {
    StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);

    @Mappings({
            @Mapping(source = "name1", target = "name2",defaultValue = "某某人"),
            @Mapping(target = "age2", expression = "java(java.lang.Integer.valueOf(student1.getAge1()))"),
            @Mapping(source = "createTime1", target = "createTime2", dateFormat = "yyyy-MM-dd HH:mm:ss:SSS"),
            @Mapping(source = "weight1", target = "weight2", numberFormat = "0.00"),
            @Mapping(target = "bloodType2", constant = "A 型血"),
            @Mapping(source = "sex1",target = "sex2",qualifiedByName = {"getBySexEnum"})
    })
    Student2 toStudent2(Student1 student1);

    @InheritConfiguration(name = "toStudent2")
    void updateStudent2(Student1 student1, @MappingTarget Student2 student2);

    @Named("getBySexEnum")
    default Integer getBySexEnum(SexEnum sexEnum){
        return sexEnum.getCode();
    }
}

對應的生成的實現類:

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2020-07-24T16:19:37+0800",
    comments = "version: 1.2.0.Final, compiler: javac, environment: Java 1.8.0_201 (Oracle Corporation)"
)
public class StudentMapperImpl implements StudentMapper {
    @Override
    public Student2 toStudent2(Student1 student1) {
        if ( student1 == null ) {
            return null;
        }

        Student2 student2 = new Student2();
        student2.setSex2( getBySexEnum( student1.getSex1() ) );
        if ( student1.getName1() != null ) {
            student2.setName2( student1.getName1() );
        }
        else {
            student2.setName2( "某某人" );
        }
        if ( student1.getCreateTime1() != null ) {
            student2.setCreateTime2( new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss:SSS" ).format( student1.getCreateTime1() ) );
        }
        if ( student1.getWeight1() != null ) {
            student2.setWeight2( new DecimalFormat( "0.00" ).format( student1.getWeight1() ) );
        }
        student2.setId( student1.getId() );
        student2.setBloodType2( "A 型血" );
        student2.setAge2( java.lang.Integer.valueOf(student1.getAge1()) );
        return student2;
    }

    @Override
    public void updateStudent2(Student1 student1, Student2 student2) {
        if ( student1 == null ) {
            return;
        }
        student2.setSex2( getBySexEnum( student1.getSex1() ) );
        if ( student1.getName1() != null ) {
            student2.setName2( student1.getName1() );
        }
        else {
            student2.setName2( "某某人" );
        }
        if ( student1.getCreateTime1() != null ) {
            student2.setCreateTime2( new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss:SSS" ).format( student1.getCreateTime1() ) );
        }
        if ( student1.getWeight1() != null ) {
            student2.setWeight2( new DecimalFormat( "0.00" ).format( student1.getWeight1() ) );
        }
        student2.setId( student1.getId() );
        student2.setBloodType2( "A 型血" );
        student2.setAge2( java.lang.Integer.valueOf(student1.getAge1()) );
    }
}

3.3、使用Spring依賴注入

    //TODO