MapStruct
发表于:2023-08-17 | 分类: 个人积累
字数统计: 1.7k | 阅读时长: 8分钟 | 阅读量:

MapStruct

多层应用通常需要在不同的对象模型(例如实体和DTOs)之间进行映射。

MapStruct是一个代码生成器,便捷的配置能很方便地解决两个Java bean内部字段之间的映射,在编译时生成setter/getter代码实现的字节码,因此在真正run的时候,速度快,类型安全。

官方文档:Reference Guide – MapStruct

DTO模式

DTO即数据传输对象(Data Transfer Objects)。该模式是一种简单的设计模式,主要目的是通过将一次单一调用的多个参数分批来减少到服务器的往返次数,在远程操作中降低网络开销。

该实践的其它好处是序列化的逻辑(转换对象结构和数据为一种能被存储和传输的指定格式的机制)的封装。它提供了在序列化细微差别中一个单一改变点。它也解耦了表示层的领域模型,允许它们独自改变。

DTOs是平整的数据结构,不包含业务逻辑,仅仅有存储、访问和最终关联序列化或解析的方法。数据从领域模型映射为 DTO,一般通过在表示层或门面层的 mapper 组件。下方的图片说明了组件间的交互:

0f07a9a8fe50547444f5e760783cdb22.png

依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>1.4.2.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.4.2.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.4.2.Final</version>
</dependency>

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//DTO:
@Data
public class CarDto {
private String make;
private int seatCount;
private String type;
}
//entity:
@Data
public class Car {
private String make;
private int numberOfSeats;

}

为了方便我们进行对象间字段的映射,我们制定一个基类接口,进行统一化的定制,如果有特殊的需要,再自己去复写基类接口中的相应方法:

1
2
3
4
5
6
7
8
9
public interface EntityConverter<D, E> {
E toEntity(D dto);

D toDto(E entity);

List<E> toEntity(List<D> dtoList);

List<D> toDto(List<E> entityList);
}

具体的转换类:

1
2
3
4
5
6
7
//如果不注入Sping容器,那么在使用时需要以下代码获取实例:
//CarMapper mapper = Mappers.getMapper(CarMapper.class);
@Mapper(componentModel = "spring")
public interface CarMapper extends EntityConverter<CarDto, Car> {
@Mapping(source = "numberOfSeats", target = "seatCount")
CarDto toDto(Car car);
}
  1. @Mapper注解中指定了componentModel"spring",即将该接口的实现类放入了IOC容器

  2. @Mapping中指定了两个字段,表示car中的字段numberOfSeats中的值映射到CarDto中的seatCount,这是因为mapstruct的映射是根据字段名来进行的,所以字段名不能对应的字段,他们之间的映射必须要 显式指定

  3. 如果存在多个映射,可以使用@Mappings

1
2
3
4
5
@Mappings({
@Mapping(source = "type.name",target = "typeName"),
@Mapping(source = ..,target = ..),
...
})

在使用MapStruct,idea2020.3版本在build项目的时候出现错误:java: Internal error in the mapping processor: java.lang.NullPointerException

解决: Setting -->Build,Execution,Deployment -->Compiler -->User-local build加上参数: -Djps.track.ap.dependencies=false

进阶属性

在映射器中忽略未映射目标属性的警告:

1
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)

@Mappering注解的特殊属性:

  • ignore:忽略该属性的转换
1
@Mappering(target="",ignore=true)
  • expression:可以使用函数转换属性
1
2
@Mappering(target="",expression="java(Integer.parseInt(user.getId()))")
UserDto userToDto(userVo user);
  • numberFormat:格式化该元素的值
  • defaultValue:设置元素默认值,如果原对象的该属性为空,则使用默认值
1
2
@Mappering(source = "salary",target="salary",numberFormat="¥#.oo",defaultValue="¥0.00")
UserDto userToDto(userVo user);

反转注解:@InheritInverseConfiguration

1
2
3
//将UserDto方法的转换规则反转
@InheritInverseConfiguration(name="userToDto")
UserVo userToVo(UserDto user);

插件推荐

MapStruct Suppot

使用示例

实体类:

Courses CoursesVO
private Long id; private Long id;
private String name; private String name;
private String code; private String code;
private Integer type; private String type;
private Boolean open; private String open;
private Long category; private Long category;
private String describe; private String describe;
private Boolean excellent; private String excellent;
private Boolean expire; private String expire;
private LocalDateTime createDate; private String createDate;
private LocalDateTime updateDate; private String updateDate;
private String createrId; private String createrId;
private String createrName; private String createrName;
private String managerId; private String managerId;
private String managerName; private String managerName;
private Boolean deleteFlag; private String deleteFlag;
private Integer status; private Integer status;
private Boolean beProtect; private String beProtect;

MapMapper接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface CoursesMapMapper {
default String booleanToString(Boolean value) {
return value ? "是" : "否";
}

default Boolean stringToboolean(String value) {
if ("是".equals(value)) {
return true;
} else if ("否".equals(value)) {
return false;
}
return null;
}

// LocalDateTime 转换为 String
default String localDateTimeToString(LocalDateTime dateTime) {
Date date = Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant());
return DateFormatUtils.format(date, "yyyy.MM.dd HH.mm.ss");
}

@Mappings({
@Mapping(target = "beProtect", expression = "java(courses.getBeProtect() ? \"是\" : \"否\")"),
@Mapping(target = "type", expression = "java(courses.getType() == 1 ? \"面授\" : (courses.getType() == 2 ? \"在线\" : \"混合\"))")
})
CoursesVO toVO(Courses courses);

List<CoursesVO> toVO(List<Courses> courses);

/**
* 将toVO方法的转换规则反转
*/
@InheritInverseConfiguration(name = "toVO")
Courses toEntity(CoursesVO coursesVO);

List<Courses> toEntity(List<CoursesVO> coursesVO);

}

MapStruct可以自动检测到我们接口的默认方法并进行使用,可以省去我们重复的mapping注解。但是容易出bug,这就是为什么只有LocalDateTime 转换为String,不过MapStruct自动实现了String转LocalDateTime。

注意:source、defaultValue 不能 和 expression 一起出现

常见报错

1
Unknown property "xxx" in result type xxx. Did you mean "null"?

原因:项目中同时使用了Lombok,两者生成代码顺序出现冲突

解决方案:

  1. 代码生成器annotationProcessor标签部分,将lombok放在mapstruct之前。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<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>
<encoding>UTF-8</encoding>
<!--自动生成代码annotationProcessor-->
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
  1. 官网解释:常见问题 (FAQ) – MapStruct
1
2
3
4
5
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</dependency>

常用的封装名称

含义 命名规范示例
存储请求体信息 XxRequest
展示层对象命名(存储返回给前端的信息) XxVo
数据传输对象(用于存储从数据库中查询的数据) XxDto
es实体类命名 XxindexDO
db实体命名 与表名相同
service接口命名 Xxservice
service实现命名 XxserviceImpl
manager,service引入多个manager进行复杂的组合业务处理 Xxmanager
dao层命名 XxManager
封装持久化组合服务(一个实体需要从db,es,redis多种存储获取) XxRepository
上一篇:
开发工具快捷键
下一篇:
负载均衡