目錄:
好,我們繼續吧,這是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; } }
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之類的盡量使用全路徑。但是如果這個java代碼會拋異常,那可能不能使用expression了,需要自定義方法然后用qualifyByName引用。另外需要注意的是,這里我是對age采用了expression,實際上這里可以自動隱式類型轉化的,沒必要使用expression,我用的目的只是為了說明問題。
@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依賴注入
之前我們是通過 StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class) 類似這種形式獲取映射器對象的。但是如果你采用的Spring框架,你也可以通過依賴注入來獲取映射器對象。你需要做的就是制定@Mapper注解的componentModel屬性為spring,然后用@Autowired注入。有條件的情況下,強烈建議采用這種形式獲取映射器,因為這樣的映射器是單例的。
@Mapper(componentModel = "spring") public interface CarMapper { CarDto carToCarDto(Car car); } @Autowired private CarMapper mapper;