mapstruct详解
文章目錄
- 功能介紹
- mapStruct是什么
- mapStruct有哪些功能
- 整合到spring中
- 基本使用
- 引入
- 示例
- 基礎映射
- 多個對象轉換為1個對象
- 自定義轉換器
- mapper使用方式
- 建議按以下方式使用
- 更多控制
- 問題總結
- 注解說明
- 精細控制
- 參考
功能介紹
mapStruct是什么
MapStruct是基于JSR 269的Java注解處理器,因此可以在命令行構建中使用(javac、Ant、Maven等等),可以在IDE內使用。用于生成類型安全的bean映射類的Java注解處理器。屬于編譯時注解,如果轉換bean內容有變化。需要手動clean下才能將變化的內容體現到class文件中。說白了就是通過注解的形式幫我們生成set,get方法。
mapStruct有哪些功能
最大的功能就是為我們轉換兩個不同的bean,或者List,比如拿java的代碼規范來說。客戶端入參–>系統的業務層入參—>db數據庫的入參;就對這3個層次來說。一個req絕對是不能透傳到db層的。為什么呢?就是為了降低耦合。提高業務的單一性,那么某一層參數有改變不會影響其他層。
整合到spring中
基本使用
接口注入和靜態方法調用
引入
<!-- https://mvnrepository.com/artifact/org.mapstruct/mapstruct --> <dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct</artifactId><version>1.3.1.Final</version> </dependency> <dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>1.3.1.Final</version> </dependency>示例
基礎映射
@Data public class PersonDto {private String name;private String sex;private Date birthday;private int age;private Double money;private String password;private Date createTime;} //----------------------------------- @Data public class PersonVo {private String fullName;private String sex;private String birthday;private int age;private Double money;private String password; } //----------------------------------- @Mapper public interface PersonMapper {PersonMapper INSTANCE = Mappers.getMapper( PersonMapper.class );@Mapping( source = "fullName",target ="name")@Mapping( target ="birthday", dateFormat = "yyyy-MM-dd")PersonDto vo2dto(PersonVo vo);@Mapping( source = "name",target ="fullName")@Mapping( target ="birthday", dateFormat = "yyyy-MM-dd")@Mapping( target ="password",ignore = true)PersonVo dto2vo(PersonDto dto);}//----------------------------------- ublic static void test1() {PersonDto dto = new PersonDto();dto.setAge(10);dto.setBirthday(Date.from(Instant.now()));dto.setMoney(2.543D);dto.setName("john");dto.setSex("F");dto.setPassword("password");dto.setCreateTime(new Date());System.out.println("dto:::" + dto.toString());PersonVo vo = PersonMapper.INSTANCE.dto2vo(dto);System.out.println("vo:::" + vo.toString());PersonDto dto2 = PersonMapper.INSTANCE.vo2dto(vo);System.out.println("dto2:::" + dto2.toString());} //--------------OUTPUT: dto:::PersonDto(name=john, sex=F, birthday=Wed Apr 07 11:53:05 CST 2021, age=10, money=2.543, password=password, createTime=Wed Apr 07 11:53:05 CST 2021) vo:::PersonVo(fullName=john, sex=F, birthday=2021-04-07, age=10, money=2.543, password=null) dto2:::PersonDto(name=john, sex=F, birthday=Wed Apr 07 00:00:00 CST 2021, age=10, money=2.543, password=null, createTime=null)處理了以下幾種情況:
- 相同屬性名稱自動隱式映射
- 屬性名不同,需要指定@mapping
- 類型不同,需要指定轉換格式
- 不需要設置的字段,使用ignore。如果字段在目標對象上不匹配的,則會自動忽略。
自動生成代碼
package demon.study.mapstruct;import java.text.ParseException; import java.text.SimpleDateFormat; import javax.annotation.Generated;@Generated(value = "org.mapstruct.ap.MappingProcessor",date = "2021-04-07T11:51:35+0800",comments = "version: 1.3.1.Final, compiler: javac, environment: Java 1.8.0_181 (Oracle Corporation)" ) public class PersonMapperImpl implements PersonMapper {@Overridepublic PersonDto vo2dto(PersonVo vo) {if ( vo == null ) {return null;}PersonDto personDto = new PersonDto();personDto.setName( vo.getFullName() );personDto.setSex( vo.getSex() );try {if ( vo.getBirthday() != null ) {personDto.setBirthday( new SimpleDateFormat( "yyyy-MM-dd" ).parse( vo.getBirthday() ) );}}catch ( ParseException e ) {throw new RuntimeException( e );}personDto.setAge( vo.getAge() );personDto.setMoney( vo.getMoney() );personDto.setPassword( vo.getPassword() );return personDto;}@Overridepublic PersonVo dto2vo(PersonDto dto) {if ( dto == null ) {return null;}PersonVo personVo = new PersonVo();personVo.setFullName( dto.getName() );personVo.setSex( dto.getSex() );if ( dto.getBirthday() != null ) {personVo.setBirthday( new SimpleDateFormat( "yyyy-MM-dd" ).format( dto.getBirthday() ) );}personVo.setAge( dto.getAge() );personVo.setMoney( dto.getMoney() );return personVo;} }多個對象轉換為1個對象
@Data public class A1 {private String a;private String b; }@Data public class A2 {private String c; }@Data public class A {private String a;private String b;private String c; }@Mapper public interface AMapper {AMapper INSTANCE = Mappers.getMapper( AMapper.class );@Mapping(source = "a1.a", target = "a")@Mapping(source = "a1.b", target = "b")@Mapping(source = "a2.c", target = "c")A two2one(A1 a1,A2 a2 ); }//-----------------output:自動生成代碼
public class AMapperImpl implements AMapper {@Overridepublic A two2one(A1 a1, A2 a2) {if ( a1 == null && a2 == null ) {return null;}A a = new A();if ( a1 != null ) {a.setA( a1.getA() );a.setB( a1.getB() );}if ( a2 != null ) {a.setC( a2.getC() );}return a;} }自定義轉換器
枚舉與Integer型不能互相轉換,需要自定義(枚舉與String 可能會轉換正確,Int向枚舉也可能轉換正確)。
不兼容的類型需要自定義轉換。
可以使用@mapper(uses),@mapping(expression)實現
@mapping(expression) 主要用于多個屬性與一個屬性的映射
public class Custom {@Getterpublic enum CustomType {EMPTY(0, "EMPTY"),A(1, "A"),B(2, "B");private Integer code;private String desc;CustomType(Integer code, String desc) {this.code = code;this.desc = desc;}@Overridepublic String toString() {return String.format("{%s,%s}", code, desc);}public static CustomType from(Integer code) {switch (code) {case 1:return CustomType.A;case 2:return CustomType.B;default:return CustomType.EMPTY;}}}@Datapublic static class CustomDto {private Date birthday;private CustomType customA;private CustomType customB;private CustomType customC;}@Datapublic static class CustomVo {private long birthday;private String customA;private Integer customB;private Integer customC;private int all;}@Mapper(uses = {Date2IntHandler.class})public interface CustomMapper {CustomMapper INSTANCE = Mappers.getMapper(CustomMapper.class);@Mapping(target = "all", expression = "java(enum2Int(dto.getCustomB()) + enum2Int(dto.getCustomA()) )")CustomVo dto2vo(CustomDto dto);CustomDto vo2dto(CustomVo vo);default CustomType int2Enum(Integer code) {return CustomType.from(code);}default Integer enum2Int(CustomType type) {return type.getCode();}}public static class Date2IntHandler {public static Date int2Date(long i) {return new Date(i);}public static long date2Int(Date date) {return date.getTime();}}public static void main(String[] args) {test();}public static void test() {CustomDto dto = new CustomDto();dto.setBirthday(Date.from(Instant.now().minus(Period.ofDays(1000))));dto.setCustomA(CustomType.A);dto.setCustomB(CustomType.B);dto.setCustomC(CustomType.B);System.out.println("dto:::" + dto.toString());CustomVo vo = CustomMapper.INSTANCE.dto2vo(dto);System.out.println("vo:::" + vo.toString());CustomDto dto2 = CustomMapper.INSTANCE.vo2dto(vo);System.out.println("dto2:::" + dto2.toString());} }輸出:
dto:::Custom.CustomDto(birthday=Thu Jul 12 14:38:04 CST 2018, customA={1,A}, customB={2,B}, customC={2,B}) vo:::Custom.CustomVo(birthday=1531377484863, customA=A, customB=2, customC=2, all=3) dto2:::Custom.CustomDto(birthday=Thu Jul 12 14:38:04 CST 2018, customA={1,A}, customB={2,B}, customC={2,B})自動生成代碼
public class Custom$CustomMapperImpl implements CustomMapper {@Overridepublic CustomVo dto2vo(CustomDto dto) {if ( dto == null ) {return null;}CustomVo customVo = new CustomVo();customVo.setBirthday( Date2IntHandler.date2Int( dto.getBirthday() ) );if ( dto.getCustomA() != null ) {customVo.setCustomA( dto.getCustomA().name() );}customVo.setCustomB( enum2Int( dto.getCustomB() ) );customVo.setCustomC( enum2Int( dto.getCustomC() ) );customVo.setAll( enum2Int(dto.getCustomB()) + enum2Int(dto.getCustomA()) );return customVo;}@Overridepublic CustomDto vo2dto(CustomVo vo) {if ( vo == null ) {return null;}CustomDto customDto = new CustomDto();customDto.setBirthday( Date2IntHandler.int2Date( vo.getBirthday() ) );if ( vo.getCustomA() != null ) {customDto.setCustomA( Enum.valueOf( CustomType.class, vo.getCustomA() ) );}customDto.setCustomB( int2Enum( vo.getCustomB() ) );customDto.setCustomC( int2Enum( vo.getCustomC() ) );return customDto;} }注意:
@mapping的屬性,source 和 expression 不能同時定義,因為expression中使用了表達式。
Maper中定義的轉換函數 ,會被自動應用到相同類型屬性的轉換上,可能會導致非期望結果。
mapper使用方式
1、接口注入方式
mapStruct接口類的聲明
//使用@Mapper(componentModel = "spring") 整合 @Mapper(componentModel = "spring") public interface ItemInfoConvert {} //------------------------------------------------------ //調用: //直接接口注入,方法調用即可@Autowiredprivate ItemInfoConvert itemInfoConvert;Person person = itemInfoConvert.deliveryDO2DTO(cart);2、靜態方法調用
mapStruct接口類的聲明
public interface ItemInfoConvert {//靜態方法ItemInfoConvert INSTANCE = Mappers.getMapper(ItemInfoConvert.class); } //--------------------------------------------- //調用: Person person= ItemInfoConvert.INSTANCE.deliveryDO2DTO(cart);建議按以下方式使用
@Data public class PersonDto {....../*** 輸入為null,輸出則為null* 為什么名字為toVo0?其實類是支持重載(overload),但是在使用stream時,區分不了是想用實例方法,還是類方法。* @param dto* @return*/public static PersonVo toVo0(PersonDto dto) {return PersonMapper.INSTANCE.dto2vo(dto);}public PersonVo toVo() {return PersonMapper.INSTANCE.dto2vo(this);}public static PersonDto from(PersonVo vo) {if (vo == null) {return null;}return PersonMapper.INSTANCE.vo2dto(vo);} } //------------------------------------ @Data public class PersonVo { ... ... public static PersonDto toDto0(PersonVo vo) {if (vo == null){return null;}return PersonMapper.INSTANCE.vo2dto(vo);}public PersonDto toDto(){return PersonMapper.INSTANCE.vo2dto(this);}public static PersonVo from(PersonDto dto){if (dto == null){return null;}return PersonMapper.INSTANCE.dto2vo(dto);} }//------------------------------------public static void test0() {PersonDto dto1 = new PersonDto();dto1.setAge(10);dto1.setBirthday(Date.from(Instant.now()));dto1.setMoney(2.543D);dto1.setName("john");dto1.setSex("F");dto1.setPassword("password");dto1.setCreateTime(new Date());PersonDto dto2 = new PersonDto();dto2.setAge(20);dto2.setBirthday(Date.from(Instant.now()));dto2.setMoney(7.543D);dto2.setName("tom");dto2.setSex("M");dto2.setPassword("password");dto2.setCreateTime(new Date());List<PersonDto> list = Arrays.asList(dto1, null, dto2);//未考慮空引用List<PersonVo> list2 = list.stream().map(dto -> dto.toVo()).collect(Collectors.toList());//考慮了空引用List<PersonVo> list3 = list.stream().map(PersonDto::toVo0).collect(Collectors.toList());} //------------------------------------更多控制
見官方文檔,可以實現:
- 內嵌屬性
- collection,maps,list,stream等
- defaultvalue等
問題總結
保證相同的數據結構,即參數的類型相同,同string 或者同Integer,類型不同無法轉換
Exception in thread "main" java.lang.NoSuchMethodError: demon.study.mapstruct.PersonVo.setBirthday(Ljava/util/Date;)V必須要使用@Mapping來解決此類問題。
避開關鍵字,比如:delete等
如果項目中也同時使用到了 Lombok,一定要注意 Lombok的版本要等于或者高于1.18.10,否則會有編譯不通過的情況發生
注解說明
-
@Mapper:注解在接口、類上,這樣 MapStruct 才會去實現該接口
屬性componentModel:該屬性用于指定實現類的類型,有幾個屬性值:
- default:默認,不使用任何組建類型,可以通過Mappers.getMapper(Class) 方式獲取實例對象
- spring:在實現類上注解 @Component,可通過 @Autowired 方式注入
- jsr330:實現類上添加@javax.inject.Named 和@Singleton注解,可以通過 @Inject注解獲取。
-
@Mappings:配置多個@Mapping
-
@Mapping:配置屬性映射,若源對象屬性與目標對象名字一致,會自動映射對應屬性
- source:源屬性、target:目標屬性
- dateFormat:可將 String 到 Date 日期之間相互轉換,通過 SimpleDateFormat,該值為 SimpleDateFormat 的日期格式
- ignore: 忽略這個字段
精細控制
控制拷貝屬性時,源字段為null時,是否覆蓋目標對象。
@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE, nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS) //進行null 檢查,如果為null,則不復制屬性值。nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT //賦值 默認值 nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE //忽略,保持目標對象屬性值The strategy works in a hierarchical fashion. @Mapping#nullValueCheckStrategy will override @BeanMapping#nullValueCheckStrategy, @BeanMapping#nullValueCheckStrategy will override @Mapper#nullValueCheckStrategy and @Mapper#nullValueCheckStrategy will override @MaperConfig#nullValueCheckStrategy.
NullValuePropertyMappingStrategy also applies when the presence checker returns not present.
參考
- MapStruct 官網:https://mapstruct.org/
- MapStruct 官方文檔:https://mapstruct.org/documentation/reference-guide/ , https://mapstruct.org/documentation/1.3/reference/html/#expressions
- MapStruct maven 地址:https://mvnrepository.com/artifact/org.mapstruct/mapstruct
- MapStruct github 地址:https://github.com/mapstruct/mapstruct/
- https://www.cnblogs.com/javaguide/p/11861749.html
總結
以上是生活随笔為你收集整理的mapstruct详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: kafka技术内幕(二)
- 下一篇: toml文件格式