文章目录

  • 前言
  • 1. 隐式类型转换
  • 2. 映射引用类型
  • 3. 嵌套映射
  • 4. 调用自定义映射方法
  • 5. 调用其他映射器
  • 6. 将映射目标类型传递给自定义映射器
  • 7. 将上下文或状态对象传递给自定义方法
  • 8. 映射方法解析
  • 9. 基于限定符的映射方法选择
  • 10. 将限定符与默认值相结合

前言

源对象和目标对象中的映射属性并不总是具有相同的类型。例如,源属性可能是int,但目标 bean 中的类型为Long。

MapStruct 如何处理此类数据类型转换的呢?

1. 隐式类型转换

一般情况下,MapStruct 会自动处理类型转换。例如,如果源对象中一个属性类型为int,但在目标对象中属于String类型,则生成的代码将分别通过调用String#valueOf(int)Integer#parseInt(String)执行转换。

目前支持以下类型自动转换

转换类型 说明
所有Java基本数据类型及其相应的包装类型 例如nt和Integer,boolean和Boolean之间等
所有 Java 基本数据类型和包装器类型之间 例如在int和long或byte和Integer之间。从较大的数据类型转换为较小的数据类型(例如 long转 int)可能会导致值或精度损失。在Mapper和MapperConfig注解有一个typeConversionPolicy来控制警告/错误。默认值为“ReportingPolicy.IGNORE”。
所有 Java 基本数据(包括包装类)和String之间 例如在int和String或Boolean和String之间。java.text.DecimalFormat可以指定响应格式字符串。
在enum类型和String之间。
在大数字类型(java.math.BigInteger, java.math.BigDecimal)和 Java 基本类型(包括包装类)以及字符串之间。 java.text.DecimalFormat可以指定响应格式字符串。
JAXBElement和T之间,List<JAXBElement>和List
java.util.Calendar/java.util.Date和 JAXB之间XMLGregorianCalendar
在java.util.Date/XMLGregorianCalendar和之间String java.text.SimpleDateFormat可以通过选项指定格式字符串
时间类转换 比如 java.sql.Timestamp和java.util.Date、java.time.LocalDateTime和javax.xml.datatype.XMLGregorianCalendar等等
java.util.Currency和String 该值必须是有效的ISO-4217字母代码,否则会抛出IllegalArgumentException
java.util.UUID和String 该值必须是有效的UUID,否则会抛出IllegalArgumentException
String和StringBuilder
java.net.URL和String

案例演示

  1. 创建源及目标对象,其中的字段类型不一致
@Data
public class Person {String name;Integer age;Date jdkDate;Float money;
}
@Data
@ToString
public class PersonDTO {String name;String age;String strDate;String money;
}
  1. 添加转换映射器
@Mapper(componentModel = "spring", typeConversionPolicy = ReportingPolicy.ERROR)
public interface PersonMapper {// 日志格式化@Mapping(source = "jdkDate", target = "strDate", dateFormat = "yyyy-MM-dd HH:mm")// 数字格式化,数字转字符串时才会生效@Mapping(source = "money", target = "money", numberFormat = "0.00")PersonDTO person2PersonDTO(Person person);
}
  1. 查看结果,按照预期进行了转换

2. 映射引用类型

通常,对象不仅具有基础数据类型,而且还有引用类型。例如,Car类可以包含对Person对象(代表汽车的司机),Person对象应该映射到类CarDto中PersonDto对象。

在这种情况下,需要映射属性为引用对象时,只需为引用的对象类型定义一个映射方法即可。

案例演示

  1. Car及CarDTO添加Person及PersonDTO属性
@Data
public class Car {public String make;public Integer numberOfSeats;public String type;public Driver driver;public Person person;
}
@Data
@ToString
//@Builder(toBuilder=true)
public class CarDto {public String name;public String make;public Integer seatCount;public String type;public String driverName;public PersonDTO personDTO;
}
  1. 映射器添加方法
    @Mapping(source = "person", target = "personDTO")CarDto car2CarDto(Car car);PersonDTO person2PersonDTO(Person person);
  1. 查看编译后的文件,可以看出MapStruct调用了在进行引用对象属性进行转换时,调用了对应的引用对象映射方法。

    在生成映射器的实现类方法时,MapStruct 将为源对象和目标对象中的每个属性对应以下规则:
  • 如果源和目标属性具有相同的类型,则该值将直接从源复制到目标。如果该属性是一个集合(例如 List),则该集合的副本将被设置到目标属性中。

  • 如果源属性类型和目标属性类型不同,检查是否存在其他映射方法,其参数类型为源属性类型,返回类型为目标属性类型。如果存在这样的方法,它将在生成的映射实现中调用。

  • 如果不存在这样的方法,MapStruct 将查看属性的源和目标类型的内置转换是否存在。如果是这种情况,生成的映射代码将应用此转换。

  • 如果不存在这样的方法,MapStruct 将应用复杂的转换:

    • 映射方法,映射方法映射的结果,像这样:target = method1( method2( source ) )
    • 内置转换,通过映射方法映射的结果,如下所示:target = method( conversion( source ) )
    • 映射方法,内置转换映射的结果,如下所示:target = conversion( method( source ) )
  • 如果没有找到这样的方法,MapStruct 将尝试生成一个自动子映射方法,该方法将在源属性和目标属性之间进行映射。

  • 如果 MapStruct 无法创建基于名称的映射方法,则会在构建时引发错误,指示不可映射的属性及其路径。

3. 嵌套映射

MapStruct 将根据源和目标属性的名称生成一个方法。不幸的是,在许多情况下,这些名称并不匹配。使用@Mapping注解可以解决这种问题,也可以解决嵌套对象字段映射问题。

@Mapper
public interface FishTankMapper {// 将源fish对象的type映射给目标对象fish属性的kind@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 );
}

4. 调用自定义映射方法

有时映射并不简单,有些字段需要自定义逻辑。
案例演示:比如数据库账户余额采用分单位,实际用户查看应该显示多少元,这个时候就可以在Mapper 中,自定义该字段的处理逻辑。

@Mapper
public abstract class CustomerMapper {public static final CustomerMapper INSTANCE = Mappers.getMapper(CustomerMapper.class);@InheritInverseConfigurationCustomerDto fromCustomer(Customer customer) {CustomerDto customerDto = new CustomerDto();customerDto.setMoney(customer.getMoney() / 100);return customerDto;}
}

5. 调用其他映射器

除了在同一映射器上定义的方法之外,MapStruct 还可以调用其他类中定义的映射方法。

案例演示:定义一个公共转换器,对时间和字符串转换进行格式化,其他转换器调用该转换器。

创建一个手动映射器类:

public class DateMapper {public String asString(Date date) {return date != null ? new SimpleDateFormat( "yyyy-MM-dd" ).format( date ) : null;}public Date asDate(String date) {try {return date != null ? new SimpleDateFormat( "yyyy-MM-dd" ).parse( date ) : null;}catch ( ParseException e ) {throw new RuntimeException( e );}}
}

在接口的@Mapper注解中引用了DateMapper,在进行映射时,MapStruct或查找DateMapper中关于时间映射的相关方法进行转换。

@Mapper(uses = DateMapper.class)
public abstract class CustomerMapper {public static final CustomerMapper INSTANCE = Mappers.getMapper(CustomerMapper.class);@Mapping(target = "name", source = "customerName")abstract Customer toCustomer(CustomerDto customerDto);abstract CustomerDto fromCustomer(Customer customer);@Mapping(target = "name", source = "customerName")abstract Customer toCustomer(Map<String, String> map);
}

查看生成的代码,可以看到调用:

6. 将映射目标类型传递给自定义映射器

当使用@Mapper#uses()引入自定义映射器时,在自定义映射器中可以定义类型为Class(或其超类型)的附加参数,以便对特定目标对象类型执行常规映射任务。必须使用@TargetType注解标识该参数。

案例演示

@ApplicationScoped // CDI component model
public class ReferenceMapper {@PersistenceContextprivate EntityManager entityManager;public <T extends BaseEntity> T resolve(Reference reference, @TargetType Class<T> entityClass) {return reference != null ? entityManager.find( entityClass, reference.getPk() ) : null;}public Reference toReference(BaseEntity entity) {return entity != null ? new Reference( entity.getPk() ) : null;}
}@Mapper(componentModel = "cdi", uses = ReferenceMapper.class )
public interface CarMapper {Car carDtoToCar(CarDto carDto);
}

然后 MapStruct 将生成如下内容:

//GENERATED CODE
@ApplicationScoped
public class CarMapperImpl implements CarMapper {@Injectprivate ReferenceMapper referenceMapper;@Overridepublic Car carDtoToCar(CarDto carDto) {if ( carDto == null ) {return null;}Car car = new Car();car.setOwner( referenceMapper.resolve( carDto.getOwner(), Owner.class ) );// ...return car;}
}

7. 将上下文或状态对象传递给自定义方法

额外的上下文或状态信息可以通过带有@Context注解的参数传递到自定义方法。此类参数会传递给其他映射方法、@ObjectFactory方法、@BeforeMapping @AfterMapping方法。

  • @Context搜索参数以查找@ObjectFactory方法,如果适用,在提供的上下文参数值上调用这些方法。

  • @Context参数也会搜索@BeforeMapping/@AfterMapping方法,如果适用,这些方法会在提供的上下文参数值上调用。

注意:null在对上下文参数调用 before/after 映射方法之前不执行任何检查。调用者需要确保null在这种情况下不会传递。

生成的代码要调用带有@Context参数声明的方法,生成的映射方法的声明也至少需要包含那些(或可分配的)@Context参数。生成的代码不会创建缺少@Context参数的新实例,也不会传递文字null。

使用@Context参数将数据向下传递到手写属性映射方法:

public abstract CarDto toCar(Car car, @Context Locale translationLocale);protected OwnerManualDto translateOwnerManual(OwnerManual ownerManual, @Context Locale locale) {// manually implemented logic to translate the OwnerManual with the given Locale
}

然后 MapStruct 将生成如下内容:

//GENERATED CODE
public CarDto toCar(Car car, Locale translationLocale) {if ( car == null ) {return null;}CarDto carDto = new CarDto();carDto.setOwnerManual( translateOwnerManual( car.getOwnerManual(), translationLocale );// more generated mapping codereturn carDto;
}

8. 映射方法解析

将属性从一种类型映射到另一种类型时,MapStruct 会查找将源类型映射到目标类型的最具体方法。该方法可以在同一个映射器接口上声明,也可以在通过@Mapper#uses()引入其他映射器, 这同样适用于工厂方法(请参阅对象工厂)。

查找映射或工厂方法的算法尽可能类似于 Java 的方法解析算法。特别是,具有更具体源类型的方法将优先(例如,如果有两种方法,一种映射搜索的源类型,另一种映射相同的超类型)。如果找到不止一种最具体的方法,则会引发错误。

使用 JAXB 时,例如将String转换为相应的 JAXBElement<String>时,MapStruct 将在查找映射方法时考虑@XmlElementDecl注解的scope和name属性。这可确保创建的JAXBElement实例具有正确的值。

9. 基于限定符的映射方法选择

在许多情况下,需要映射具有相同方法参数(名称除外)但具有不同行为的方法。比如在隐射器中,添加对某个字段转换时候需要翻译成中文或英文。

    public String translateTitleEG(String title) {// some mapping logicreturn "英文";}public String translateTitleChinese(String title) {// some mapping logicreturn "中文";}

这个时候编译,会报错:

如果不使用限定符,这将导致不明确的映射方法错误,因为找到了 2 个限定方法 ( translateTitleEG, translateTitleChinese) 并且 MapStruct 不会提示选择哪一个。

MapStruct提供了@Qualifier(org.mapstruct.Qualifier)注解来解决这个问题。

首先我们使用@Qualifier创建三个注解:

@Qualifier
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface TitleTranslator {}
@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface ChineseTitle {}
@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface EnglishTitle {}

在Mapper 中添加上面的注解,方法上使用不同的注解标识:

@TitleTranslator
public class BaseMapper {@EnglishTitlepublic String translateTitleEG(String title) {// some mapping logicreturn "英文";}@ChineseTitlepublic String translateTitleChinese(String title) {// some mapping logicreturn "中文";}
}

在映射器转换对象的方法中,使用qualifiedBy 选择使用哪个注解标识的方法来处理该字段:

    @Mapping( target = "title", qualifiedBy = { TitleTranslator.class, ChineseTitle.class } )CarDto car2CarDto(Car car);

测试发现,可以正常使用我们指定的方法进行转换:

MapStruct 还提供了@Named注解,可以使用更简单的方式来进行限定使用,或者更直接地通过它的值来命名一个映射方法。上面的相同示例如下所示:

@Named("TitleTranslator")
public class Titles {@Named("EnglishToGerman")public String translateTitleEG(String title) {// some mapping logic}@Named("GermanToEnglish")public String translateTitleGE(String title) {// some mapping logic}
}
@Mapper( uses = Titles.class )
public interface MovieMapper {@Mapping( target = "title", qualifiedByName = { "TitleTranslator", "EnglishToGerman" } )GermanRelease toGerman( OriginalRelease movies );}

10. 将限定符与默认值相结合

@Mapping注解的defaultValue 属性,可以指定一个字符串类型的默认值。Mapping#qualifiedByNameMapping#qualifiedBy强制MapStruct 使用其指定的方法。

可以结合defaultValuequalifiedBy属性,放入参的值为null 时,defaultValue默认值将被传递给qualifiedBy指定的方法。

比如以下代码中:

    @Mapping( target = "title", qualifiedBy = { TitleTranslator.class, ChineseTitle.class } ,defaultValue = "defalut")CarDto car2CarDto(Car car);

如果title字段为null,将会调用translateTitleChinese方法,入参为defalut

   @ChineseTitlepublic String translateTitleChinese(String title) {// some mapping logicSystem.out.println(title);return "中文";}

MapStruct系列(5)-映射器数据类型转换详解相关推荐

  1. Java程序员从笨鸟到菜鸟之(四十四)细谈struts2(七)数据类型转换详解

     本文来自:曹胜欢博客专栏.转载请注明出处:http://blog.csdn.net/csh624366188 Web应用程序的交互都是建立在HTTP之上的,互相传递的都是字符串.也就是说服务器接收到 ...

  2. Mysql 数据类型转换详解 (convert、cast)

    文章目录 1 概述 2 类型转换 3 扩展 3.1 日期时间函数 1 概述 #mermaid-svg-ItXfZsGMo79eou3H {font-family:"trebuchet ms& ...

  3. VC常见数据类型转换详解

    我们先定义一些常见类型变量借以说明   int  i  =  100;   long  l  =  2001;   float  f=300.2;   double  d=12345.119;   c ...

  4. C语言数据类型转换详解

    数据类型转换就是将数据(变量.数值.表达式的结果等)从一种类型转换为另一种类型. 自动类型转换 自动类型转换就是编译器默默地.隐式地.偷偷地进行的数据类型转换,这种转换不需要程序员干预,会自动发生. ...

  5. C++ 数据类型转换详解之终极无惑

    程序开发环境:VS2017+Win32+Debug 文章目录 1. 隐式数据类型转换 2. 显示数据类型转换 3.C++ 新式类型转换 3.1 const_cast 3.2 static_cast 3 ...

  6. JS数据类型转换详解

    文章内容为所看网课笔记,如有侵权请联系删除 ## JS数据类型 1. 基本数据类型 number.string.undefined.null.boolean.symbol.bigint 2. 引用数据 ...

  7. Mysql 数据类型转换详解

    1 概述 #mermaid-svg-rJkseuZTXKS4FgYE {font-family:"trebuchet ms",verdana,arial,sans-serif;fo ...

  8. Oracle 数据类型转换详解(显示 + 隐式)

    文章目录 1 概述 2 显式转换 2.1 to_char 2.2 to_date 2.3 to_number 3 隐式转换 1 概述 #mermaid-svg-fwPpwf4wV1eBbxmS .la ...

  9. Python数据类型转换详解(内附详细案例)

    「作者主页」:士别三日wyx   此文章已录入专栏<Python入门到精通>   2021最新版Python小白教程,针对0基础小白和基础薄弱的伙伴学习 提示:点击列表中蓝色「函数名」可直 ...

最新文章

  1. 瞎聊Spring Cloud
  2. aryson ms sql_数据治理:SQL数据清洗十八般武艺
  3. 浅谈HSRP(热备份路由选择协议)
  4. 红外传感器_基于红外避障传感器控制无人机
  5. c++获取数组长度_灵魂拷问:Java如何获取数组和字符串的长度?length还是length()?...
  6. php 处理 http 请求,PHP的http请求处理类
  7. 如何通过调试的方式搞清楚Angular createEmbeddedView具体创建的UI元素是什么
  8. openwrt 格式化_OPENWRT扩展系统到U盘
  9. vim 插件cscope 使用
  10. 另类玩法:通过 DNS 进行文件传输
  11. java简述常见的布局极其特点_请简要说明 Android 中五种常见布局的特点。_学小易找答案...
  12. Aria2在Windows上如何安装配置使用
  13. 队列仿真一个银行叫号系统
  14. snagit 9注册码
  15. NCH ClickCharts(流程图绘制软件)v4.10 汉化免费版
  16. oracle 11203 ora32701,love wife love life —Roger的Oracle/MySQL数据恢复博客
  17. shader篇-动画
  18. DQM Serial Sync Index Program ERROR
  19. 新电脑 安装idea迁移设置和插件注意事项
  20. 函数模板和类模板详解

热门文章

  1. <stm32学习笔记>--基本定时器TIM6TIM7
  2. 开发者百度地图的使用,做一个小demo,ak秘钥,
  3. 对方接住了你人的php,接住你了表情包 - 接住你了微信表情包 - 接住你了QQ表情包 - 发表情 fabiaoqing.com...
  4. 麒麟芯片配上鸿蒙系统有多快,麒麟芯片和鸿蒙系统靠边站,纯国产飞腾芯片和麒麟系统早已大规模使用...
  5. odoo8 openerp 入门
  6. python列表输出学生姓名学号链表_c语言!!!程序设计:建立一个学生信息链表,包括学号,姓名,成绩.(实现添加,删除,查询,排序,平均)...
  7. IEEE 754 32bit浮点标识
  8. 模糊查询忽略大小写解决方案
  9. 微信小程序实现柱形图与折现图
  10. 点星PBX(DotAsterisk)外线呼入到离线坐席(sip分机未注册)时,如何播放语音提示外线客户坐席不在线