隐式类型转换
MapStruct在很多情况下可以自动进行类型转换. 比如: source类型为Int, target类型为String, 在进行相互转换时, 生成的Mapper代码中会自动透明的调用
String.valueOf(int)或Integer.parseInt(string).
目前MapStruct有以下自动隐式转换:
- 所有Java原始数据类型 对应的包装类型之间
- 如int与Integer, boolean与Boolean
- 对null是感知的. 将null的包装类型转成对应的原始类型时, 对会null进行检查.
- 所有Java原始数字类型 对应的包装类型之间
- 如 int与long, 或者 byte与Integer
- 从大数转成小数时, 会有值或者精度损失. 可以通过
Mapper或者MapperConfig注解的typeConversionPolicy来控制warning或者errors. 当前的默认值为:ReportingPolicy.IGNORE 所有的Java原始类型(包括对应的包装类) 与String之间.
- 如int与String, 或者Boolean与String.
可以使用
Mapping注解的**numberFormat**属性来指定格式字符串 (java.text.DecimalFormat), 用来制定转换格式. 如下//将Double转成String, 并限定数字格式@Mapperpublic interface CarMapper {@Mapping(source = "price", numberFormat = "$#.00")CarDto carToCarDto(Car car);@IterableMapping(numberFormat = "$#.00")List<String> prices(List<Integer> prices);}
在enum与String之间
在大数[
**BigInteger**,**BigDecimal**]与小数(及包装类)之间, 及String之间- 可以使用
**@Mapping**注解的**numberFormat**属性来指定格式字符串 (java.text.DecimalFormat), 用来制定转换格式. 如下://从BigDecima转成String@Mapperpublic interface CarMapper {@Mapping(source = "power", numberFormat = "#.##E0")CarDto carToCarDto(Car car);}
- 可以使用
日期(java.util.Date)与String之间
- 可以使用
**@Mapping**注解的**dateFormat**属性来指定格式字符串 (java.text.SimpleDateFormat), 用来制定转换格式. 如下: ```java @Mapper public interface CarMapper { @Mapping(source = “manufacturingDate”, dateFormat = “dd.MM.yyyy”) CarDto carToCarDto(Car car);
@IterableMapping(dateFormat = “dd.MM.yyyy”) List
stringListToDateList(List dates); } ``` - 可以使用
各种时间/日期类型与
**java.util.Date**之间的转换.java.sql.Date与java.util.Datejava.sql.Time与java.util.Datejava.sql.Timestamp与java.util.Date- 其他类型的时间, 如:
java.time.Instant,java.time.LocalDate等
映射引用类型
一个对象里基本不会只有基础类型的属性, 基本都会有引用类型的. 如
Car类里里面的Person driver属性即为引用类型.
这种场景下, 只需要为需要映射的引用类型也写一个mapping映射方法即可.
如下: Car到CarDTO 映射时需要处理Person到PersonDTO的映射. 在同一个Mapper中增加相应的映射方法即可.
@Mapperpublic interface CarMapper {CarDto carToCarDto(Car car);PersonDto personToPersonDto(Person person);}
框架为carToCarDto()生成的代码中, 会调用personToPersonDto()来映射driver属性.
所以这样以来, 理论是可以做无限层深度的映射对象graph.
映射规则🔥🔥
- source与target名字与类型相同时, value
简单直接被copy到target. 如果属性是一个collection集合类型, 集合中的元素会被copy到target属性中. - 如果source与target类型不同, 框架会检查是否存在另个一个
mapping方法(以source属性类型为参数类型, 以target属性类型为返回类型). 如果有这种的方法, 则会在生成的mapping方法实现中调用该方法. - 如果没有这种方法时, MapStruct会查找是否存在
source到target的 内置的类型转换 . 如果有, 则直接应用此类型转换. - 如果也没有找到适配的内置类型, 则MapStruct会使用==[复合转换]==. (见下面 MappingControl)
- 如果这样的方法也没找到的话, MapStruct会尝试生成一个自动
sub-mapping映射方法, 来做source与target之间的映射. - 如果MapStruct无法创建一个基于name的映射方法的话, 会在编译时报错, 说明没有可映射的属性和路径.
如果需要禁止MapStruct生成第5条所说的
自动sub-mapping方法, 可以使用 @Mapper( disableSubMappingMethodsGeneration = true ).
映射控制 MappingControl🔥
可以在所有水平上做映射控制(
MappingControl) 在这四种注解的mappingControl属性中设置. @MapperConfig, @Mapper, @BeanMapping, @Mapping 后者的优先级高于前者.例如:@Mapper(mappingControl = NoComplexMapping.class)优先于@MapperConfig( mappingControl =DeepClone.class).
**MappingControl**共有四种对应的选项: (*默认情况是四种选项全部开启.)🔥🔥
- MappingControl.Use#DIRECT
- 允许sourceType到targetType的直接映射
- 这意味着, 当source和target类型一致时, mapStruct不再执行任何映射, 而是直接将source的值赋值给target.
- MappingControl.Use#MAPPING_METHOD
- 允许sourceType到targetType的方法映射(**官方API中写的是directMapping, 应该是个笔误.)
- 方法可以是自定义的reffered方法, 也可以是mapStruct的内置映射方法.
- MappingControl.Use#BUILT_IN_CONVERSION
- MappingControl.Use#COMPLEX_MAPPING
**映射方法mapping嵌套**, 如: target = method1( method2( source ) )**映射方法**+**内置转换**, 如 target = method( conversion( source ) )**内置转换**+**映射方法**+ , 如 target = conversion( method( source ) )
✅**@DeepClone**表示只允许MappingMethod映射, 当source与target类型相同时, 进行source的深拷贝.
✅构造器属性 也被认为是source/target属性.
嵌套Bean映射控制
这里主要是想讲, 对于嵌套的bean, 想要做不同层级的属性映射时, 该如何做.
如下:
- FishTank- MaterialType material- Interior interior- Ornament ornament- FishTankDTO- MaterialDTO material- MapterialType materialType- OrnamentDto ornament
在@Mapping中使用**"."**符号来进行层级导航进行属性匹配.⭐️
@Mapperpublic interface FishTankMapper {@Mapping(target = "fish.kind", source = "fish.type")@Mapping(target = "fish.name", ignore = true)@Mapping(target = "ornament", source = "interior.ornament")@Mapping(target = "material.materialType", source = "material")@Mapping(target = "quality.report.organisation.name", source = "quality.report.organisationName")FishTankDto map( FishTank source );}
调用自定义的mapping方法
有些时间, 映射关系并没有直接赋值/copy那么简单, 可能会有一定的映射逻辑. 这时候, 就需要自己来编写映射方法了.
调用其他Mapper🔥
使用@Mapper(uses = {AMapper.class, BMapper.class,….} )的方式来引用其他Mapper中的mapping方法.
@Mapper(uses=DateMapper.class)public interface CarMapper {CarDto carToCarDto(Car car);}
基于qualifier进行mapping方法选择⭐️
在许多场景下, 需要映射方法有相同的签名(name除外, 相同的入参和出参类型), 但有不同的逻辑/行为. MapStruct提供了使用@Qualifier(org.mapstruct.Qualifier)的机制进行解决.
以下两种方式都可行. 需要注意的是, 如果使用@Named方式, 当修改方法名时, 引用的地方不会修改, 需要手动修改.
方式1-使用@Qualifier自定义元注释
@Mapper(uses = RoundingUtil.class)public interface SourceTargetMapper {SourceTargetMapper INSTANCE = Mappers.getMapper(SourceTargetMapper.class);@Mapping(target = "bd2", qualifiedBy = RoundingUtil.Fraction2.class)@Mapping(target = "bd3", qualifiedBy = RoundingUtil.Fraction3.class)@Mapping(target = "bdUnConstrained")Target toTarget(Source source);}
public class RoundingUtil {@Qualifier@Target(ElementType.METHOD)@Retention(RetentionPolicy.CLASS)public @interface Fraction2 {}@Qualifier@Target(ElementType.METHOD)@Retention(RetentionPolicy.CLASS)public @interface Fraction3 {}@Fraction2public BigDecimal fraction2(BigDecimal in) {return in == null ? null : in.setScale(2, RoundingMode.HALF_UP);}@Fraction3public BigDecimal fraction3(BigDecimal in) {return in == null ? null : in.setScale(3, RoundingMode.HALF_UP);}}
方式2-使用@Named注解
@Mapper(uses = RoundingUtil.class)public interface SourceTargetMapper {SourceTargetMapper INSTANCE = Mappers.getMapper(SourceTargetMapper.class);@Mapping(target = "bd2", qualifiedByName = "Fraction2")@Mapping(target = "bd3", qualifiedByName = "Fraction3")@Mapping(target = "bdUnConstrained")Target toTarget(Source source);}
public class RoundingUtil {@Named("Fraction2")public BigDecimal fraction2(BigDecimal in) {return in == null ? null : in.setScale(2, RoundingMode.HALF_UP);}@Named("Fraction3")public BigDecimal fraction3(BigDecimal in) {return in == null ? null : in.setScale(3, RoundingMode.HALF_UP);}}
