源对象和目标对象中的映射属性并不总是具有相同的类型。例如,源属性可能是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);
    @InheritInverseConfiguration
    CustomerDto 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 {
    @PersistenceContext
    private 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 {
    @Inject
    private ReferenceMapper referenceMapper;
    @Override
    public 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 code
    return carDto;

8. 映射方法解析

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

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

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

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

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

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

这个时候编译,会报错:
在这里插入图片描述
如果不使用限定符,这将导致不明确的映射方法错误,因为找到了 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 {
    @EnglishTitle
    public String translateTitleEG(String title) {
        // some mapping logic
        return "英文";
   @ChineseTitle
    public String translateTitleChinese(String title) {
        // some mapping logic
        return "中文";

在映射器转换对象的方法中,使用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

   @ChineseTitle
    public String translateTitleChinese(String title) {
        // some mapping logic
       System.out.println(title);
        return "中文";
                    数据类型转换源对象和目标对象中的映射属性并不总是具有相同的类型。例如,属性可能int属于源 bean 中的类型Long,但属于目标 bean 中的类型。MapStruct 如何处理此类数据类型转换的呢?1. 隐式类型转换在许多情况下,MapStruct 会自动处理类型转换。例如,如果源 bean 中一个属性类型为int,但在目标 bean 中属于String类型,则生成的代码将分别通过调用String#valueOf(int)和Integer#parseInt(String)透明地执行转换。目前支
   在我们Java开发的过程中不可避免的会遇到需要将一个类转换为另一个类的情况,比如我们从数据中或者别人的接口中查询出来的类转换为我们对外展示所用的视图类。可能有人会说,那直接用返回的类当做视图类不就可以了,还省了转换的步骤和时间。但是如果当返回内容增加或修改时就很容易污染我们的对外视图类,所以类转换也变得不可或缺。下面将展示四种种类转换方式。
类型转换方式一:
CategoryVo categoryVo = JSONObject.parseObject(JSONObje...
				
MapStruct 是一个代码生成器,它基于约定优于配置方法极大地简化了 Java bean 类型之间映射的实现。自动生成的映射转换代码只使用简单的方法调用,因此速度快、类型安全而且易于理解阅读;本篇就是实现 SpringBoot 整合 MapStruct 实现数据类型转化。 本篇内容包括:项目介绍与条件准备、项目搭建与构造、效果验证
<groupId>org.mapstruct</groupId> <artifactId>mapstruct‐jdk8</artifactId> <version>1.2.0.Final</version> </dependency> <dependency> <groupId>org.mapstruct</groupId>
在工作中经验需要用到类型转换–例如将一个对象的值赋给另一个对象。这时候如果采取重复set、get的方法,会造成大量代码冗余,且没有技术含量;写起来枯燥乏味。那么我们可以引入一些第三方框架来做这个事情:MapStruct 先说下背景下:Maven父子项目中,数据库对应的实体类放在server层,查询出来的对象需要返回给前端,但是web层引用不到server层的实体类,这时候需要新建一个对象(属性与实...
翻阅官方文档https://mapstruct.org/documentation/stable/reference/html/#datatype-conversions 发现官方有个例子,关于自定义转换器规则的例子 When generating code for the implementation of thecarToCarDto()method, MapStruct will ...
对于MapStruct类型转换,您可以按照以下步骤进行操作: 1. 首先,在您的项目中添加MapStruct依赖。您可以在Maven或Gradle构建工具中添加相应的依赖项。 2. 创建源类型(source type)和目标类型(target type),这些类型可以是POJO(普通Java对象)。 3. 在源类型和目标类型之间创建一个Mapper接口。该接口应该使用`@Mapper`注解进行标记,并且可以定义多个转换方法。 4. 在转换方法中,使用`@Mapping`注解指定源类型和目标类型之间的映射关系。您可以使用属性名称、表达式或自定义转换器来定义映射规则。 5. 在您的代码中使用`Mapper`接口生成的实现类,通过调用转换方法进行类型转换。 下面是一个简单的示例: ```java // 源类型 public class Source { private String name; private int age; // getter和setter方法省略 // 目标类型 public class Target { private String fullName; private int yearsOld; // getter和setter方法省略 // Mapper接口 @Mapper public interface SourceTargetMapper { @Mapping(source = "name", target = "fullName") @Mapping(source = "age", target = "yearsOld") Target sourceToTarget(Source source); // 使用转换器 public class Main { public static void main(String[] args) { Source source = new Source(); source.setName("John"); source.setAge(25); SourceTargetMapper mapper = Mappers.getMapper(SourceTargetMapper.class); Target target = mapper.sourceToTarget(source); System.out.println(target.getFullName()); // 输出: John System.out.println(target.getYearsOld()); // 输出: 25 通过以上步骤,您可以使用MapStruct进行源类型和目标类型之间的转换。请注意,MapStruct还支持更复杂的转换场景,例如集合映射和嵌套映射。您可以在MapStruct的官方文档中了解更多详细信息。
Access to XMLHttpRequest at ‘http://xx‘ from origin ‘http://xx‘ has been blocked by CORS policy: 60036