mapstruct详解
文章目錄
- 功能介紹
- mapStruct是什么
- mapStruct有哪些功能
- 整合到spring中
- 基本使用
- 引入
- 示例
- 基礎(chǔ)映射
- 多個(gè)對(duì)象轉(zhuǎn)換為1個(gè)對(duì)象
- 自定義轉(zhuǎn)換器
- mapper使用方式
- 建議按以下方式使用
- 更多控制
- 問(wèn)題總結(jié)
- 注解說(shuō)明
- 精細(xì)控制
- 參考
功能介紹
mapStruct是什么
MapStruct是基于JSR 269的Java注解處理器,因此可以在命令行構(gòu)建中使用(javac、Ant、Maven等等),可以在IDE內(nèi)使用。用于生成類(lèi)型安全的bean映射類(lèi)的Java注解處理器。屬于編譯時(shí)注解,如果轉(zhuǎn)換bean內(nèi)容有變化。需要手動(dòng)clean下才能將變化的內(nèi)容體現(xiàn)到class文件中。說(shuō)白了就是通過(guò)注解的形式幫我們生成set,get方法。
mapStruct有哪些功能
最大的功能就是為我們轉(zhuǎn)換兩個(gè)不同的bean,或者List,比如拿java的代碼規(guī)范來(lái)說(shuō)。客戶端入?yún)ⅷC>系統(tǒng)的業(yè)務(wù)層入?yún)ⅰ?gt;db數(shù)據(jù)庫(kù)的入?yún)?#xff1b;就對(duì)這3個(gè)層次來(lái)說(shuō)。一個(gè)req絕對(duì)是不能透?jìng)鞯絛b層的。為什么呢?就是為了降低耦合。提高業(yè)務(wù)的單一性,那么某一層參數(shù)有改變不會(huì)影響其他層。
整合到spring中
基本使用
接口注入和靜態(tài)方法調(diào)用
引入
<!-- 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>示例
基礎(chǔ)映射
@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)處理了以下幾種情況:
- 相同屬性名稱(chēng)自動(dòng)隱式映射
- 屬性名不同,需要指定@mapping
- 類(lèi)型不同,需要指定轉(zhuǎn)換格式
- 不需要設(shè)置的字段,使用ignore。如果字段在目標(biāo)對(duì)象上不匹配的,則會(huì)自動(dòng)忽略。
自動(dòng)生成代碼
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;} }多個(gè)對(duì)象轉(zhuǎn)換為1個(gè)對(duì)象
@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:自動(dòng)生成代碼
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;} }自定義轉(zhuǎn)換器
枚舉與Integer型不能互相轉(zhuǎn)換,需要自定義(枚舉與String 可能會(huì)轉(zhuǎn)換正確,Int向枚舉也可能轉(zhuǎn)換正確)。
不兼容的類(lèi)型需要自定義轉(zhuǎn)換。
可以使用@mapper(uses),@mapping(expression)實(shí)現(xiàn)
@mapping(expression) 主要用于多個(gè)屬性與一個(gè)屬性的映射
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})自動(dòng)生成代碼
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 不能同時(shí)定義,因?yàn)閑xpression中使用了表達(dá)式。
Maper中定義的轉(zhuǎn)換函數(shù) ,會(huì)被自動(dòng)應(yīng)用到相同類(lèi)型屬性的轉(zhuǎn)換上,可能會(huì)導(dǎo)致非期望結(jié)果。
mapper使用方式
1、接口注入方式
mapStruct接口類(lèi)的聲明
//使用@Mapper(componentModel = "spring") 整合 @Mapper(componentModel = "spring") public interface ItemInfoConvert {} //------------------------------------------------------ //調(diào)用: //直接接口注入,方法調(diào)用即可@Autowiredprivate ItemInfoConvert itemInfoConvert;Person person = itemInfoConvert.deliveryDO2DTO(cart);2、靜態(tài)方法調(diào)用
mapStruct接口類(lèi)的聲明
public interface ItemInfoConvert {//靜態(tài)方法ItemInfoConvert INSTANCE = Mappers.getMapper(ItemInfoConvert.class); } //--------------------------------------------- //調(diào)用: Person person= ItemInfoConvert.INSTANCE.deliveryDO2DTO(cart);建議按以下方式使用
@Data public class PersonDto {....../*** 輸入為null,輸出則為null* 為什么名字為toVo0?其實(shí)類(lèi)是支持重載(overload),但是在使用stream時(shí),區(qū)分不了是想用實(shí)例方法,還是類(lèi)方法。* @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());} //------------------------------------更多控制
見(jiàn)官方文檔,可以實(shí)現(xiàn):
- 內(nèi)嵌屬性
- collection,maps,list,stream等
- defaultvalue等
問(wèn)題總結(jié)
保證相同的數(shù)據(jù)結(jié)構(gòu),即參數(shù)的類(lèi)型相同,同string 或者同Integer,類(lèi)型不同無(wú)法轉(zhuǎn)換
Exception in thread "main" java.lang.NoSuchMethodError: demon.study.mapstruct.PersonVo.setBirthday(Ljava/util/Date;)V必須要使用@Mapping來(lái)解決此類(lèi)問(wèn)題。
避開(kāi)關(guān)鍵字,比如:delete等
如果項(xiàng)目中也同時(shí)使用到了 Lombok,一定要注意 Lombok的版本要等于或者高于1.18.10,否則會(huì)有編譯不通過(guò)的情況發(fā)生
注解說(shuō)明
-
@Mapper:注解在接口、類(lèi)上,這樣 MapStruct 才會(huì)去實(shí)現(xiàn)該接口
屬性componentModel:該屬性用于指定實(shí)現(xiàn)類(lèi)的類(lèi)型,有幾個(gè)屬性值:
- default:默認(rèn),不使用任何組建類(lèi)型,可以通過(guò)Mappers.getMapper(Class) 方式獲取實(shí)例對(duì)象
- spring:在實(shí)現(xiàn)類(lèi)上注解 @Component,可通過(guò) @Autowired 方式注入
- jsr330:實(shí)現(xiàn)類(lèi)上添加@javax.inject.Named 和@Singleton注解,可以通過(guò) @Inject注解獲取。
-
@Mappings:配置多個(gè)@Mapping
-
@Mapping:配置屬性映射,若源對(duì)象屬性與目標(biāo)對(duì)象名字一致,會(huì)自動(dòng)映射對(duì)應(yīng)屬性
- source:源屬性、target:目標(biāo)屬性
- dateFormat:可將 String 到 Date 日期之間相互轉(zhuǎn)換,通過(guò) SimpleDateFormat,該值為 SimpleDateFormat 的日期格式
- ignore: 忽略這個(gè)字段
精細(xì)控制
控制拷貝屬性時(shí),源字段為null時(shí),是否覆蓋目標(biāo)對(duì)象。
@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE, nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS) //進(jìn)行null 檢查,如果為null,則不復(fù)制屬性值。nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT //賦值 默認(rèn)值 nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE //忽略,保持目標(biāo)對(duì)象屬性值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 官網(wǎng):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
總結(jié)
以上是生活随笔為你收集整理的mapstruct详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: kafka技术内幕(二)
- 下一篇: toml文件格式