隐式类型转换
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, 并限定数字格式
@Mapper
public 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
@Mapper
public 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.Date
java.sql.Time
与java.util.Date
java.sql.Timestamp
与java.util.Date
- 其他类型的时间, 如:
java.time.Instant
,java.time.LocalDate
等
映射引用类型
一个对象里基本不会只有基础类型的属性, 基本都会有引用类型的. 如
Car
类里里面的Person driver
属性即为引用类型.
这种场景下, 只需要为需要映射的引用类型也写一个mapping映射方法即可.
如下: Car到CarDTO
映射时需要处理Person到PersonDTO
的映射. 在同一个Mapper中增加相应的映射方法即可.
@Mapper
public 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中使用**"."**
符号来进行层级导航进行属性匹配.⭐️
@Mapper
public 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 {
}
@Fraction2
public BigDecimal fraction2(BigDecimal in) {
return in == null ? null : in.setScale(2, RoundingMode.HALF_UP);
}
@Fraction3
public 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);
}
}