mapstruct-plus/docs/guide/class-convert.md
2023-12-15 08:13:46 +08:00

509 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: 两个类之间的转换
order: 1
category:
- 指南
description: MapStructPlus 两个类之间的转换及配置 class convert
---
## 简单转换
**要实现两个类之间的转换,只需要在其中一个类上增加注解 `@AutoMapper` ,配置 `target` 属性,指定目标类即可**
例如:
```java
@AutoMapper(target = CarDto.class)
public class Car {
// ...
}
```
该例子表示,会生成 `Car` 转换为 `CarDto` 的接口 `CarToCarDtoMapper` 及实现类 `CarToCarDtoMapperImpl`。在生成的转换代码中,源类型(`Car`)的所有可读属性将被复制到目标属性类型(`CarDto`)的相应属性中。
当一个属性与它的目标实体对应物具有相同的名称时,将会被隐式映射。
除此之外MapStructPlus 会根据当前的默认规则,生成 `CarDto` 转换为 `Car` 的接口 `CarDtoToCarMapper` 及实现类 `CarDtoToCarMapperImpl`。如果不想生成该转换逻辑的话,可以通过注解的 `reverseConvertGenerate` 属性来配置。
## 自定义对象的属性自动转换
当要转换的类中,存在自定义类时,会自动寻找该类型的转换方法。
例如,分别有两组对象模型:汽车(`Car`)和座椅(`SeatConfiguration`),其中 `Car` 依赖于 `SeatConfiguration`
分别对应对象如下:
- 汽车模型
:::: code-group
::: code-group-item Car
```java
@AutoMapper(target = CarDto.class)
@Data
public class Car {
private SeatConfiguration seatConfiguration;
}
```
:::
::: code-group-item CarDto
```java
@Data
public class CarDto {
private SeatConfigurationDto seatConfiguration;
}
```
:::
::::
- 座椅模型
:::: code-group
::: code-group-item SeatConfiguration
```java
@Data
@AutoMapper(target = SeatConfigurationDto.class)
public class SeatConfiguration {
// fields
}
```
:::
::: code-group-item SeatConfigurationDto
```java
@Data
public class SeatConfigurationDto {
// fields
}
```
:::
::::
在上面的例子中,首先会生成 `CarToCarDtoMapper` 和 `SeatConfigurationToSeatConfigurationDtoMapper` 两个转换接口,并且在转换 `Car` 时,会自动使用 `SeatConfigurationToSeatConfigurationDtoMapper` 来对其中的座椅属性来进行转换。
## 引入自定义类型转换器
当不同类型的属性,想要按照自定义的规则进行转换时,可以有两种办法:
1. 通过 `@AutoMapping` 中配置的 `expression` 表达式配置
2. 自定义一个类型转换器,通过 `@AutoMapper` 的 `uses` 属性来引入
方式一可以参考下面的[表达式](#表达式)章节。
这里基于方式二,实现将 `String` 类型的属性,根据逗号分隔,转换为 `List<String>` 类型的属性:
首先,定义一个类型转换器 —— `StringToListString`
```java
@Component
public class StringToListString {
public List<String> stringToListString(String str) {
return StrUtil.split(str);
}
}
```
::: warning
- 类型转换器提供的类型转换方法,可以定义为 `static` 或 `nonstatic`的。
- 如果是基于 `SpringBoot` 的方式使用该框架,则类型转换器需要定义为 Spring 的一个 Bean。
:::
下一步,使用该类型转换器:
```java
@AutoMapper(target = User.class, uses = StringToListStringConverter.class)
public class UserDto {
private String username;
private int age;
private boolean young;
@AutoMapping(target = "educationList")
private String educations;
// ......
}
```
测试:
```java
@SpringBootTest
public class QuickStartTest {
@Autowired
private Converter converter;
@Test
public void ueseTest() {
UserDto userDto = new UserDto();
userDto.setEducations("1,2,3");
final User user = converter.convert(userDto, User.class);
System.out.println(user.getEducationList()); // [1, 2, 3]
assert user.getEducationList().size() == 3;
}
}
```
## 自定义属性转换
当两个类中属性存在不一致的场景时,例如名称、类型等不一致,可以进行自定义转换,通过在属性上面添加 `@AutoMapping`,来配置映射规则。
### 不同属性名称映射
`@AutoMapping` 注解中,提供了 `target` 属性,可以配置当前属性与目标类中 `target` 属性之间映射。
例如,`Car` 转换为 `CatDto` 时,`seatConfiguration` 属性与 `seat` 属性相对应:
```java
@AutoMapper(target = CarDto.class)
@Data
public class Car {
@AutoMapping(target = "seat")
private SeatConfiguration seatConfiguration;
}
```
`@AutoMapping` 注解中还提供 `source` 方法,该配置默认取当前属性的名称,之所以可以配置,是为了适应一种场景,当前类的某个属性,其内部的属性,转换为目标中的属性字段,则可以通过当前属性来配置。
例如:
:::: code-group
::: code-group-item Goods
```java
@Data
@AutoMapper(target = GoodsVo.class, reverseConvertGenerate = false)
public class Goods {
@AutoMapping(source = "sku.price", target = "price")
private Sku sku;
}
```
:::
::: code-group-item GoodsVo
```java
@Data
public class GoodsVo {
private Integer price;
}
```
:::
::::
### 指定时间格式转换
当时间类型(例如:`Date`、`LocalDateTime`、`LocalDate` 等等)需要和 `String` 通过指定时间格式进行转换时,可以通过 `@AutoMapping` 中的 `dateFormat` 来配置:
例如:
:::: code-group
::: code-group-item Order
```java
@Data
@AutoMapper(target = OrderEntity.class)
public class Order {
@AutoMapping(dateFormat = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime orderTime;
@AutoMapping(dateFormat = "yyyy_MM_dd HH:mm:ss")
private Date createTime;
@AutoMapping(target = "orderDate", dateFormat = "yyyy-MM-dd")
private String date;
}
```
:::
::: code-group-item OrderEntity
```java
@Data
@AutoMapper(target = Order.class)
public class OrderEntity {
@AutoMapping(dateFormat = "yyyy-MM-dd HH:mm:ss")
private String orderTime;
@AutoMapping(dateFormat = "yyyy_MM_dd HH:mm:ss")
private String createTime;
@AutoMapping(target = "date", dateFormat = "yyyy-MM-dd")
private LocalDate orderDate;
}
```
:::
::::
### 指定数字格式转换
当数字类型(例如:`int`/`Integer` 等数字基本类型及包装类、`BigDecimal`)和 `String` 之间的转换需要指定数字格式,可以通过 `@AutoMapping` 的 `numberFormat` 来配置。
> 该格式需要 `java.text.DecimalFormat` 所支持
例如:
:::: code-group
::: code-group-item Order
```java
@Data
@AutoMapper(target = OrderEntity.class)
public class Order {
@AutoMapping(numberFormat = "$0.00")
private BigDecimal orderPrice;
@AutoMapping(numberFormat = "$0.00")
private Integer goodsNum;
}
```
:::
::: code-group-item OrderEntity
```java
@Data
@AutoMapper(target = Order.class)
public class OrderEntity {
@AutoMapping(numberFormat = "$0.00")
private String orderPrice;
@AutoMapping(numberFormat = "$0.00")
private String goodsNum;
}
```
:::
::::
### 忽略指定属性的转换
当在进行转换时,需要忽略指定属性的转换,可以通过 `@AutoMapping` 的 `ignore` 来配置。
例如:
```java
@AutoMapper(target = CarDto.class)
@Data
public class Car {
@AutoMapping(target = "wheels", ignore = true)
private Wheels wheels;
}
```
### 属性转换时的默认值
`@AutoMapping` 中的 `defaultValue` 可以指定在转换属性时,当属性为 `null` 时,转换到目标类中的默认值。
例如:
```java
@Data
@AutoMapper(target = DefaultVo.class)
public class DefaultDto {
@AutoMapping(defaultValue = "18")
private Integer i;
@AutoMapping(defaultValue = "1.32")
private Double d;
@AutoMapping(defaultValue = "true")
private Boolean b;
}
```
### 表达式
在执行属性转换时,可以通过指定执行一段 Java 代码来进行转换操作,例如,对源对象中的某个属性进行转换后返回。
需要注意的是,在生成时,会直接将表达式插入到转换逻辑中,并不会验证其语法。
例如,将源对象中的 `List<String>` 属性,通过 `,` 拼接为字符串:
```java
@AutoMapper(target = UserDto.class)
public class User {
@AutoMapping(target = "educations", expression = "java(java.lang.String.join(\",\", source.getEducationList()))")
private List<String> educationList;
}
```
## 自动接入自定义转换接口
::: info
since `1.2.3`
:::
当有的类型转换逻辑比较复杂,可以通过自定义转换接口来实现,即使用 MapStruct 原生的方式。
当使用这种方式时,默认生成的类型转换中,如果有前面提供的类型转换时,会自动引用。
例如:
:::: code-group
::: code-group-item Car
```java
@AutoMapper(target = CarDto.class)
@Data
public class Car {
private Tyre tyre;
}
```
:::
::: code-group-item CarDto
```java
@Data
public class CarDto {
private TyreDTO tyre;
}
```
:::
::::
这里定义 `Tyre` 和 `TyreDTO` 之间的转换接口:
```java
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface TyreMapper {
TyreDTO tyreToTyreDTO(Tyre tyre);
Tyre tyreDtoToTyre(TyreDTO tyreDTO);
}
```
生成的 `Car` 与 `CarDto` 转换接口的实现类如下:
:::: code-group
::: CarToCarDtoMapperImpl
```java
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2023-04-24T15:38:48+0800",
comments = "version: 1.5.5.Final, compiler: javac, environment: Java 1.8.0_202 (Oracle Corporation)"
)
@Component
public class CarToCarDtoMapperImpl implements CarToCarDtoMapper {
@Autowired
private TyreMapper tyreMapper;
@Override
public CarDto convert(Car source) {
if ( source == null ) {
return null;
}
CarDto carDto = new CarDto();
carDto.setTyre( tyreMapper.tyreToTyreDTO( source.getTyre() ) );
return carDto;
}
@Override
public CarDto convert(Car source, CarDto target) {
if ( source == null ) {
return target;
}
target.setTyre( tyreMapper.tyreToTyreDTO( source.getTyre() ) );
return target;
}
}
```
:::
::: CarDtoToCarMapperImpl
```java
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2023-04-24T15:38:49+0800",
comments = "version: 1.5.5.Final, compiler: javac, environment: Java 1.8.0_202 (Oracle Corporation)"
)
@Component
public class CarDtoToCarMapperImpl implements CarDtoToCarMapper {
@Autowired
private TyreMapper tyreMapper;
@Override
public Car convert(CarDto source) {
if ( source == null ) {
return null;
}
Car car = new Car();
car.setTyre( tyreMapper.tyreDtoToTyre( source.getTyre() ) );
return car;
}
@Override
public Car convert(CarDto source, Car target) {
if ( source == null ) {
return target;
}
target.setTyre( tyreMapper.tyreDtoToTyre( source.getTyre() ) );
return target;
}
}
```
:::
::::
## 反向属性映射配置
::: info
**在该文中,所有提到的源类指通过 `@AutoMapper` 注解的类;目标类指的是 `@AutoMapper` 中 `target` 属性指定的类型。**
:::
前面提到,当在一个类上面添加 `@AutoMapper` 注解时,默认情况下,除了会生成源类到目标类的转换接口,还会生成目标类到源类的转换接口和实现类,这里需要注意的是,默认情况下生成的该转换接口,并没有任何自定义配置,即使在源类中配置了 `@AutoMapping` 注解。
这里要实现目标类到源类的自定义转换配置,可以有两种方式:
1. 在目标类上面添加 `@AutoMapper` 注解。这是最建议的方式,当转换双方都有添加该注解时,便不会生成默认的转换接口,即按照自定义的规则进行生成。
2. 当目标类访问不到源类,或者项目规范不允许在目标类上面添加该种注解时,可以将自定义配置全部添加在源类中。这就是下面要介绍的**反向属性映射配置**。
框架中提供了 `@ReverseAutoMapping` 注解,该注解就是为了配置目标类到源类的自定义转换规则。
::: warning
这里需要注意的是,防止配置冲突,一旦添加 `@ReverseAutoMapping` 注解,在目标类中,便不能添加任何自定义转换注解
:::
**`@ReverseAutoMapping` 注解表示的含义,是目标类到源类转换时,需要指定的自定义转换规则,其中可以配置的属性,与 `@AutoMapping` 注解一致。**
这里有两个属性需要注意,分别是 `source` 和 `target`。
**这里的 `source` 指的是目标类中的属性,`target` 指的是源类中的属性。**
可能会有人这里有疑问,为什么这里的配置像是反的?如果没有,可以直接跳过。
框架设计的时候,所有的属性转换配置,都是基于要转换的类型,最终想要的效果是将该类转换为目标类。这里的 `source` 也应该是来源类中的属性。
如果还是不理解,这里可以认为,该注解就是本该应用在目标类中的 `@AutoMapping` 注解,原封不动拷贝到当前类,再修改注解名称即可。
## 不可变类型设计
> since 1.3.2
当一个类型是不可变类型时,之前默认的规则,生成的 `T convert(S source, @MappingTarget T target)` 可能会存在问题。
所以,可以使用任意包下的 `Immutable` 注解,标识一个类为不可变类型,
当为不可变类型时,`@MappingTarget` 没有意义,上面的方法最终生成如下:
```java
public T convert(S source, @MappingTarget T target) {
return target;
}
```