Mapstruct 使用教程

Mapstruct 版本1.4.1.Final
官方文档
案例-github

前言

MapStruct是一个Java注释处理器,用于生成类型安全的bean映射类。

您要做的就是定义一个映射器接口,该接口声明任何必需的映射方法。在编译期间,MapStruct将生成此接口的实现。此实现使用简单的Java方法调用在源对象和目标对象之间进行映射,即没有反射或类似内容。

与手动编写映射代码相比,MapStruct通过生成繁琐且易于出错的代码来节省时间。遵循配置方法上的约定,MapStruct使用合理的默认值,但在配置或实现特殊行为时不加理会。
与动态映射框架相比,MapStruct具有以下优点:

  1. 通过使用普通方法调用(settter/getter)而不是反射来快速执行
  2. 编译时类型安全性:只能映射相互映射的对象和属性,不能将order实体意外映射到customer DTO等。
  3. 如果有如下问题,编译时会抛出异常
    3.1 映射不完整(并非所有目标属性都被映射)
    3.2 映射不正确(找不到正确的映射方法或类型转换)

1. 设置

MapStruct是基于JSR 269的Java注释处理器,因此可以在命令行构建(javac,Ant,Maven等)以及您的IDE中使用。

它包含以下工件:

  1. org.mapstruct:mapstruct:包含必需的注释,例如@Mapping

  2. org.mapstruct:mapstruct-processor:包含注释处理器,该注释处理器生成映射器实现

1.1 Maven

对于基于Maven的项目,将以下内容添加到您的POM文件中以使用MapStruct:

...
<properties>
    <org.mapstruct.version>1.4.1.Final</org.mapstruct.version>
</properties>
...
<dependencies>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>${org.mapstruct.version}</version>
    </dependency>
</dependencies>
...
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${org.mapstruct.version}</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>
...

1.1.1集成lombok

...
<properties>
    <org.mapstruct.version>1.4.1.Final</org.mapstruct.version>
    <org.projectlombok.version>1.18.12</org.projectlombok.version>
</properties>
...
<dependencies>
	<dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>${org.mapstruct.version}</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${org.projectlombok.version}</version>
        <scope>provided</scope>
     </dependency>
</dependencies>
...
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${org.mapstruct.version}</version>
                    </path>
                    <path>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
                        <version>${org.projectlombok.version}</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>
...

2. 定义一个映射器

2.1 基本映射

要创建映射器,只需使用所需的映射方法定义一个Java接口,并用注释对其进行org.mapstruct.Mapper注释:
该@Mapper注释将使得MapStruct代码生成器创建的执行PersonMapper 过程中生成时的界面。

在生成的方法实现中,源类型(例如Person)的所有可读属性都将被复制到目标类型(例如PersonDTO)的相应属性中:

  1. 当一个属性与其目标实体对应的名称相同时,它将被隐式映射。

  2. 当属性在目标实体中具有不同的名称时,可以通过@Mapping注释指定其名称。

如果不指定@Mapping,默认映射name相同的field
如果映射的对象field name不一样,通过 @Mapping 指定。

@Data
public class Person {

    String describe;

    private String id;

    private String name;

    private int age;

    private BigDecimal source;

    private double height;

    private Date createTime;
	
}

@Data
public class PersonDTO {

    String describe;

    private Long id;

    private String personName;

    private String age;

    private String source;

    private String height;
    
}
// mapper
@Mapper
public interface PersonMapper {

    PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);
	
	@Mapping(target = "name", source = "personName")
    PersonDTO conver(Person person);

}

生成的实现类:

 public class PersonMapperImpl implements PersonMapper {
    public PersonMapperImpl() {
    }

    public PersonDTO conver(Person person) {
        if (person == null) {
            return null;
        } else {
            PersonDTO personDTO = new PersonDTO();
            personDTO.setDescribe(person.getDescribe());
            if (person.getId() != null) {
                personDTO.setId(Long.parseLong(person.getId()));
            }

            personDTO.setPersonName(person.getName());
            personDTO.setAge(String.valueOf(person.getAge()));
            if (person.getSource() != null) {
                personDTO.setSource(person.getSource().toString());
            }

            personDTO.setHeight(String.valueOf(person.getHeight()));
            return personDTO;
        }
    }
}

测试:

@Test
public void test(){
     Person person = new Person();
     person.setDescribe("测试");
     person.setAge(18);
     person.setName("张三");
     person.setHeight(170.5);
     person.setSource(new BigDecimal("100"));

     PersonDTO dto = PersonMapper.INSTANCT.conver(person);

     System.out.println(dto);
    // PersonDTO(describe=测试, id=null, personName=张三, age=18, source=100, height=170.5)
}

2.2 指定默认值

在@Mapper接口类里面的转换方法上添加@Mapping注解
target() 必须添加,source()可以不添加,则直接使用defaultValue

@Mapping(target = "describe", source = "describe", defaultValue = "默认值")
PersonDTO conver(Person person);

生成的impl:

...
if (person.getDescribe() != null) {
    personDTO.setDescribe(person.getDescribe());
 } else {
     personDTO.setDescribe("默认值");
 }
 ...

测试:


@Test
public void test(){
	Person person = new Person();
	//person.setDescribe("测试");
	person.setAge(18);
	person.setName("张三");
	person.setHeight(170.5);
	person.setSource(new BigDecimal("100"));
	
	PersonDTO dto = PersonMapper.INSTANCT.conver(person);
	System.out.println(dto);
   // PersonDTO(describe=默认值, id=null, name=张三, age=18, source=100, height=170.5)
}

2.3 使用expression()

目前java是唯一受支持的语言,达式必须以Java表达式的形式给出
注意: 这个属性不能与source()、defaultValue()、defaultExpression()、qualifiedBy()、qualifiedByName()或constant()一起使用。

@Mapping(target = "describe", source = "describe", defaultValue = "默认值")
@Mapping(target = "createTime",expression = "java(new java.util.Date())")
PersonDTO conver(Person person);

测试:

@Test
public void test(){
     Person person = new Person();
     //person.setDescribe("测试");
     person.setAge(18);
     person.setName("张三");
     person.setHeight(170.5);
     person.setSource(new BigDecimal("100"));

     PersonDTO dto = PersonMapper.INSTANCT.conver(person);

     System.out.println(dto);
	// PersonDTO(describe=默认值, id=null, name=张三, age=18, source=100, height=170.5, createTime=Fri Dec 11 23:21:31 GMT+08:00 2020)

 }

2.4 dateFormat()

如果属性从字符串映射到日期,则该格式字符串可由SimpleDateFormat处理,反之亦然。当映射枚举常量时,将忽略所有其他属性类型。

....
@Mapping(target = "createTime" ,source = "createTime", dateFormat = "yyyy-MM-dd")
PersonDTO conver(Person person);
...

impl:

try {
	if (person.getCreateTime() != null) {
    	personDTO.setCreateTime((new SimpleDateFormat("yyyy-MM-dd")).parse(person.getCreateTime()));
	}
} catch (ParseException var4) {
    throw new RuntimeException(var4);
}

2.5 组合映射

2.5.1 多个源对象

@Data
public class BasicEntity {

    private Date createTime;

    private String createBy;

    private Date updateTime;

    private String updateBy;

    private int _ROW;

}

@Mapper(uses =DateFormtUtil.class)
public interface PersonMapper {

    PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);

    @Mapping(target = "personName",source = "name")
    PersonDTO conver(Person person);

    @Mapping(target = "createTime",source = "basicEntity.createTime")
    PersonDTO combinationConver(Person personC, BasicEntity basicEntity);

}

2.5.2 使用其他的值

...
@Mapping(target = "id", source = "id")
PersonDTO mapTo(Person person, String id);
...

虽然Person和Person有相同的id字段,但是映射器会使用mapTo方法里面的id参数。

2.6 嵌套映射

@Data
public class Person {
	...
	private Child personChild;
	...
}
@Data
public class PersonDTO {
	...
    private Child child;
    ...
}
// mapper
@Mapper(uses =DateFormtUtil.class)
public interface PersonMapper {

    PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);

    @Mapping(target = "child", source = "personChild")
    PersonDTO conver(Person person);

}

如果field name一样则不需要指定@Mapping

2.7 numberFormat()

如果带注释的方法从数字映射到字符串,则使用DecimalFormat将格式字符串作为可处理的格式。反之亦然。对于所有其他元素类型,将被忽略。
从基本2.1 基本映射可以看出,number类型与字符串直接的转换是通过valueOf(),如果字符串格式不正确会抛出java.lang.NumberFormatException异常,例如:Integer.valueOf(“10.2”)
使用numberFormat()之后DecimalFormat格式转换,还是会抛出NFE异常

// mapper
....
@Mapping(target = "age",source = "age", numberFormat = "#0.00")
PersonDTO conver(Person person);
...
// imppl
personDTO.setAge((new DecimalFormat("#0.00")).format((long)person.getAge()));

可以使用自定义方法进行处理

3. 使用自定义方法

3.1 自定义类型转换方法

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:

@Mapper(uses=DateMapper.class)
public interface PersonMapper{
  PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);
  PersonDTO conver(Person person);
}

impl:

public class PersonMapperImpl implements PersonMapper {
    private final DateMapper dateMapper = new DateMapper();

    public PersonMapperImpl() {
    }

    public PersonDTO conver(Person person) {
      ....
      personDTO.setCreateTime(this.dateMapper.asDate(person.getCreateTime()));
	  ...
      return personDTO;
       
    }
}

在进行类型转换的时候直接调用改转换方法
@Mapper#uses可以使用多个类

3.2 使用@Qualifier

@Qualifier 标记的自定义注解标记的方法,必须有输入, 否则编译时会抛出异常

public class DateFormtUtil {

    @DateFormat
    public static String dateToString(Date date){
        return date == null ? "": new SimpleDateFormat("yyyy-MM-dd").format(date);
    }

    @Qualifier
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.CLASS)
    public @interface DateFormat{}
}

mapper:

@Mapper(uses =DateFormtUtil.class)
public interface PersonMapper {

    PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);

    @Mapping(target = "createTime",source = "createTime",qualifiedBy = DateFormat.class)
    PersonDTO conver(Person person);

}

3.3 使用@namd

public class DateFormtUtil {

    @Named("dateToString")
    public static String dateToString(Date date){
        return date == null ? "": new SimpleDateFormat("yyyy-MM-dd").format(date);
    }
}

mapper:

@Mapper(uses =DateFormtUtil.class)
public interface PersonMapper {

    PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);

   @Mapping(target = "createTime",source = "createTime",qualifiedByName = "dateToString")
    PersonDTO conver(Person person);
}

效果跟@Qualifier是一样的

4.集合映射

MapStruct有CollectionMappingStrategy,与可能的值:ACCESSOR_ONLYSETTER_PREFERREDADDER_PREFERREDTARGET_IMMUTABLE

在下表中,破折号-表示属性名称。接下来,尾部s表示复数形式。该表解释了这些选项以及它们是如何施加到存在/不存在的set-sadd-和/或get-s在目标对象上的方法:

选项 仅目标set-s可用 仅目标add-可用 既可以set-s/add- 没有set-s/add- 现有目标(@TargetType
ACCESSOR_ONLY set-s get-s set-s get-s get-s
SETTER_PREFERRED set-s add- set-s get-s get-s
ADDER_PREFERRED set-s add- add- get-s get-s
TARGET_IMMUTABLE set-s exception set-s exception set-s

5.集成到 spring

@Mapper#componentModel 中指定依赖注入框架

@Mapper(componentModel = "spring")
public interface ModelMapper {

    ModelMapper INSTANT = Mappers.getMapper(ModelMapper.class);

    ModelVO conver(Model model);

}
// 直接在类中使用Autowired注入就行了
@RestController
class MapperSpringController {

    @Autowired
    ModelMapper modelMapper;

    @GetMapping("/get")
    ModelVO getModle(){
       Model model = new Model();
       model.setId("123456");
       model.setName("张三");
       model.setCreate(new Date());
       return modelMapper.conver(model);
    }
}

6 高级运用

6.1 spi的运用

官方文档 关于spi的运用描述

6.2 freemarker生成代码

github链接

上一篇:java常用API -FILE+MapStruct


下一篇:MapStruct入门指南