MapStruct 实战

MapStruct 实战

前言

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

我们需要做的就是定义一个映射接口,声明映射方法。在编译期间,MapStruct 将生成此接口的实现类。此实现使用简单的 Java 方法调用(getter setter…)在源对象和目标对象之间进行属性映射,没有使用反射或类似的内容。

与手动编写属性映射代码相比,MapStruct 通过生成冗长且易于出错的代码来节省时间。按照约定优于配置,MapStruct 使用合理的默认值(用户没有自定义配置或实现特殊行为)。

与动态映射框架相比,MapStruct 具有以下优点:

  • 通过使用普通方法调用而不是反射来快速执行
  • 编译时类型安全性:只能映射彼此映射的对象和属性(不能将订单实体意外映射到客户DTO等)。
  • 编译期错误报告:
    • 映射不完整(并非所有目标属性都被映射)
    • 映射不正确(找不到正确的映射方法或类型转换)

引入依赖

项目使用 Maven 构建,其他方式如 GradleAnt 等请查考官网 设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...
<properties>
<org.mapstruct.version>1.3.1.Final</org.mapstruct.version>
</properties>
...
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>

<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
</dependencies>
...

基本使用

基本映射

要创建映射器,只需定义一个 Java 接口,并使用 org.mapstruct.Mapper 注解:

1
2
3
4
5
6
7
8
@Mapper
public interface StudentMapper {

StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);

@Mapping(source = "staffName", target = "studentName")
Student toStudent(Staff staff);
}

@Mapper 注解将使用 MapStruct 的代码生成器创建 StudentMapper 接口的实现 StudentMapperImpl,在生成的方法实现中,源类型(例如Staff)的所有可读属性都将被复制到目标类型(例如Student)的相应属性中:

  • 当一个属性与其目标实体对应的名称相同时,它将被隐式映射。
  • 当属性在目标实体中具有不同的名称时,可以通过@Mapping 注解指定其名称。

StudentMapperImpl.class 反编译如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class StudentMapperImpl implements StudentMapper {
public StudentMapperImpl() {
}

public Student toStudent(Staff staff) {
if (staff == null) {
return null;
} else {
Student student = new Student();
student.setStudentName(staff.getStaffName());
return student;
}
}
}

单元测试如下:

1
2
3
4
5
6
@Test
public void test1() {
Staff staff = PojoUtil.getStaffInstance();
Student student = StudentMapper.INSTANCE.toStudent(staff);
assert Objects.equals(staff.getStaffName(), student.getStudentName());
}

多源参数映射

多源参数映射即多对一映射,使用 @Mapping(source = "student.studentName", target = "name") 可以将 studentstudentName 属性 映射(复制)到 Personname 上,代码如下:

1
2
3
4
5
6
7
@Mapper
public interface PersonMapper {

@Mapping(source = "student.studentName", target = "name")
@Mapping(source = "staff.staffNo", target = "personNo")
Person toPerson(Student student, Staff staff);
}

更新现有Bean

在某些情况下,不需要创建目标类型的新实例,而是更新该类型的现有实例。可以通过为目标对象添加一个参数并将其标记为 @MappingTarget, 其中,person1的同名属性会完全覆盖(更新)person2的同名属性,如下

1
2
3
4
5
6

@Mapper
public interface PersonMapper {

void update(Person person1, @MappingTarget Person person2);
}

使用依赖注入

这里使用 Spring 容器来介绍 MapStruct 如何使用依赖注入,我们需要通过 Mapper#componentModel 来指定DI 类型,目前支持

  • default:映射器不使用组件模型,实例通常通过Mappers.getmapper(类)获取。

  • cdi:生成的映射器是应用程序范围的cdi bean,可以通过@Inject 获取。

  • spring:生成的映射器是一个spring bean,可以通过@Autowired 获取。

  • jsr330:生成的映射器用`@javax.inject.named@singleton注释,可以通过@inject` 获取。

实例代码如下:

1
2
3
4
@Mapper(componentModel = "spring")
public interface PersonMapper {
...
}

调用其他映射器

虽然我们可以通过 @Mapping 注解来灵活的映射不同实体的属性对应关系,但是遇到日期类型格式化为字符串类型,我们不得不复制粘贴例如 @Mapping(target = "registryDate", dateFormat = "dd.MM.yyyy") 这样的映射关系,有没有什么更为简单通用的实现呢,MapStruct 提供了 Mapper#uses ,我们可以自定义一些高级映射,例如 DateMapper,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Component
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);
}
}
}

// 指定 DateMapper
@Mapper(componentModel = "spring", uses = DateMapper.class)
public interface PersonMapper {
...
}

这样就能将 Date registryDate 通过自定义的转换方式转化为 String registryDate

补充说明

这里补充一些 Mapping的其他用法:

  • 忽略映射字段: @Mapping(target = "gender", ignore = true)
  • 日期格式化: @Mapping(target = "registryDate", dateFormat = "dd.MM.yyyy")
  • 数值格式化: @Mapping(source = "price", numberFormat = "$#.00")
  • 表达式映射: @Mapping(target = "timeAndFormat", expression = "java(new org.sample.TimeAndFormat( s.getTime(), s.getFormat())
  • 默认表达式: @Mapping(target="id", source="sourceId", defaultExpression = "java( UUID.randomUUID().toString() )")

其他用法,如有兴趣,可移步 官方文档

参考文档

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×