隐式类型转换

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), 用来制定转换格式. 如下

      1. //将Double转成String, 并限定数字格式
      2. @Mapper
      3. public interface CarMapper {
      4. @Mapping(source = "price", numberFormat = "$#.00")
      5. CarDto carToCarDto(Car car);
      6. @IterableMapping(numberFormat = "$#.00")
      7. List<String> prices(List<Integer> prices);
      8. }
  • 在enum与String之间

  • 在大数[**BigInteger**, **BigDecimal**]与小数(及包装类)之间, 及String之间

    • 可以使用**@Mapping**注解的**numberFormat**属性来指定格式字符串 (java.text.DecimalFormat), 用来制定转换格式. 如下:
      1. //从BigDecima转成String
      2. @Mapper
      3. public interface CarMapper {
      4. @Mapping(source = "power", numberFormat = "#.##E0")
      5. CarDto carToCarDto(Car car);
      6. }
  • 日期(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.Datejava.util.Date
    • java.sql.Timejava.util.Date
    • java.sql.Timestampjava.util.Date
    • 其他类型的时间, 如: java.time.Instant, java.time.LocalDate

映射引用类型

一个对象里基本不会只有基础类型的属性, 基本都会有引用类型的. 如 Car类里里面的Person driver属性即为引用类型.

这种场景下, 只需要为需要映射的引用类型也写一个mapping映射方法即可.

如下: Car到CarDTO 映射时需要处理Person到PersonDTO的映射. 在同一个Mapper中增加相应的映射方法即可.

  1. @Mapper
  2. public interface CarMapper {
  3. CarDto carToCarDto(Car car);
  4. PersonDto personToPersonDto(Person person);
  5. }

框架为carToCarDto()生成的代码中, 会调用personToPersonDto()来映射driver属性.

所以这样以来, 理论是可以做无限层深度的映射对象graph.

映射规则🔥🔥

  1. source与target名字与类型相同时, value简单直接被copy到target. 如果属性是一个collection集合类型, 集合中的元素会被copy到target属性中.
  2. 如果source与target类型不同, 框架会检查是否存在另个一个mapping方法 (以source属性类型为参数类型, 以target属性类型为返回类型). 如果有这种的方法, 则会在生成的mapping方法实现中调用该方法.
  3. 如果没有这种方法时, MapStruct会查找是否存在source到target内置的类型转换 . 如果有, 则直接应用此类型转换.
  4. 如果也没有找到适配的内置类型, 则MapStruct会使用==[复合转换]==. (见下面 MappingControl)
  5. 如果这样的方法也没找到的话, MapStruct会尝试生成一个自动sub-mapping映射方法, 来做source与target之间的映射.
  6. 如果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, 想要做不同层级的属性映射时, 该如何做.

如下:
image.png

  1. - FishTank
  2. - MaterialType material
  3. - Interior interior
  4. - Ornament ornament
  5. - FishTankDTO
  6. - MaterialDTO material
  7. - MapterialType materialType
  8. - OrnamentDto ornament

在@Mapping中使用**"."**符号来进行层级导航进行属性匹配.⭐️

  1. @Mapper
  2. public interface FishTankMapper {
  3. @Mapping(target = "fish.kind", source = "fish.type")
  4. @Mapping(target = "fish.name", ignore = true)
  5. @Mapping(target = "ornament", source = "interior.ornament")
  6. @Mapping(target = "material.materialType", source = "material")
  7. @Mapping(target = "quality.report.organisation.name", source = "quality.report.organisationName")
  8. FishTankDto map( FishTank source );
  9. }

调用自定义的mapping方法

有些时间, 映射关系并没有直接赋值/copy那么简单, 可能会有一定的映射逻辑. 这时候, 就需要自己来编写映射方法了.

调用其他Mapper🔥

使用@Mapper(uses = {AMapper.class, BMapper.class,….} )的方式来引用其他Mapper中的mapping方法.

  1. @Mapper(uses=DateMapper.class)
  2. public interface CarMapper {
  3. CarDto carToCarDto(Car car);
  4. }

基于qualifier进行mapping方法选择⭐️

在许多场景下, 需要映射方法有相同的签名(name除外, 相同的入参和出参类型), 但有不同的逻辑/行为. MapStruct提供了使用@Qualifier(org.mapstruct.Qualifier)的机制进行解决.

以下两种方式都可行. 需要注意的是, 如果使用@Named方式, 当修改方法名时, 引用的地方不会修改, 需要手动修改.

方式1-使用@Qualifier自定义元注释

  1. @Mapper(uses = RoundingUtil.class)
  2. public interface SourceTargetMapper {
  3. SourceTargetMapper INSTANCE = Mappers.getMapper(SourceTargetMapper.class);
  4. @Mapping(target = "bd2", qualifiedBy = RoundingUtil.Fraction2.class)
  5. @Mapping(target = "bd3", qualifiedBy = RoundingUtil.Fraction3.class)
  6. @Mapping(target = "bdUnConstrained")
  7. Target toTarget(Source source);
  8. }
  1. public class RoundingUtil {
  2. @Qualifier
  3. @Target(ElementType.METHOD)
  4. @Retention(RetentionPolicy.CLASS)
  5. public @interface Fraction2 {
  6. }
  7. @Qualifier
  8. @Target(ElementType.METHOD)
  9. @Retention(RetentionPolicy.CLASS)
  10. public @interface Fraction3 {
  11. }
  12. @Fraction2
  13. public BigDecimal fraction2(BigDecimal in) {
  14. return in == null ? null : in.setScale(2, RoundingMode.HALF_UP);
  15. }
  16. @Fraction3
  17. public BigDecimal fraction3(BigDecimal in) {
  18. return in == null ? null : in.setScale(3, RoundingMode.HALF_UP);
  19. }
  20. }

方式2-使用@Named注解

  1. @Mapper(uses = RoundingUtil.class)
  2. public interface SourceTargetMapper {
  3. SourceTargetMapper INSTANCE = Mappers.getMapper(SourceTargetMapper.class);
  4. @Mapping(target = "bd2", qualifiedByName = "Fraction2")
  5. @Mapping(target = "bd3", qualifiedByName = "Fraction3")
  6. @Mapping(target = "bdUnConstrained")
  7. Target toTarget(Source source);
  8. }
  1. public class RoundingUtil {
  2. @Named("Fraction2")
  3. public BigDecimal fraction2(BigDecimal in) {
  4. return in == null ? null : in.setScale(2, RoundingMode.HALF_UP);
  5. }
  6. @Named("Fraction3")
  7. public BigDecimal fraction3(BigDecimal in) {
  8. return in == null ? null : in.setScale(3, RoundingMode.HALF_UP);
  9. }
  10. }