MapStruct高级拷贝工具的使用

igxiaoshan Lv5

MapStruct高级拷贝工具的使用

MapStruct中,可以通过多种方式实现复杂的自定义映射和嵌套对象的映射。MapStruct提供了强大的注释和配置功能来处理这些复杂场景。

如何使用 MapStruct

1、引入依赖

pom.xml中添加MapStruct依赖(maven版)

1
2
3
4
5
6
7
8
9
10
11
12
13
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.5.Final</version> <!-- 请根据需要替换为最新版本 -->
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.5.Final</version>
<scope>provided</scope>
</dependency>
</dependencies>

2、定义Mapper接口

需要定义一个Mapper接口,MapStruct会在编译时生成实现类。

假设现在有两个类SourceTargetDto,可以创建如下Mapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

// 定义Mapper接口
@Mapper
public interface SourceToDtoMapper {

// 获取Mapper实例
SourceToDtoMapper INSTANCE = Mappers.getMapper(SourceToDtoMapper.class);

// 定义映射方法
TargetDTO convertToDto(Source source);
}

3、调用Mapper

在代码中调用这个Mapper来完成对象转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import org.springframework.cglib.core.ReflectUtils;

public static <S, T> T convertObjectToDto(S source, Class<T> dtoClass) {
if (source == null || dtoClass == null) {
throw new IllegalArgumentException("数据源对象或DTO类类型不能为空");
}
try {
// 使用缓存的构造函数来优化性能
Constructor<T> constructor = dtoClass.getDeclaredConstructor();
constructor.setAccessible(true);
T dto = constructor.newInstance();

// 使用更高效的映射工具,例如 MapStruct 或 ModelMapper
BeanUtils.copyProperties(source, dto);

return dto;
} catch (Exception e) {
throw new RuntimeException("无法将数据源对象转换为DTO对象", e);
}
}

总结

  • 高性能:MapStruct在编译时生成代码,避免了运行时反射操作的性能开销
  • 简化嵌套对象映射:通过MapStruct自动处理嵌套对象的映射,避免手动递归
  • 可维护性:使用MapStruct接口定义映射规则,代码更直观可读,维护性更高。

高级用法

自定义字段映射

如果源对象和目标对象的字段名称不同,或者需要对某些字段进行特定的转换,可以使用**@Mappering**注解来自定义映射

示例

假设有两个类 SourceTargetDTO,其中 Sourcename 字段需要映射到 TargetDTOfullName 字段,birthDate 字段需要转换为 age

1
2
3
4
5
6
7
8
9
10
11
12
public class Source {
private String name;
private LocalDate birthDate;
// getters and setters
}

public class TargetDTO {
private String fullName;
private int age;
// getters and setters
}

通过 @Mapping 注解自定义字段映射,使用自定义方法进行 birthDateage 的转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

import java.time.LocalDate;
import java.time.Period;

@Mapper
public interface SourceToDtoMapper {

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

// 自定义映射规则
@Mapping(source = "name", target = "fullName")
@Mapping(source = "birthDate", target = "age", qualifiedByName = "calculateAge")
TargetDTO convertToDto(Source source);

// 自定义转换方法
default int calculateAge(LocalDate birthDate) {
return Period.between(birthDate, LocalDate.now()).getYears();
}
}

嵌套对象映射

当源对象和目标对象中包含嵌套对象时,MapStruct可以自动处理这些嵌套对象的映射,或者可以显示指定如何处理嵌套的映射。

示例

假设 SourceTargetDTO 都包含嵌套对象 Address,你希望将 Source 的嵌套对象也映射到 TargetDTO

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

public class Source {
private String name;
private Address address;
// getters and setters
}

public class Address {
private String street;
private String city;
// getters and setters
}

public class TargetDTO {
private String fullName;
private AddressDTO address;
// getters and setters
}

public class AddressDTO {
private String street;
private String city;
// getters and setters
}

MapStruct 会自动识别并映射嵌套对象。如果需要,可以单独定义一个映射方法来处理嵌套对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14

@Mapper
public interface SourceToDtoMapper {

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

// 映射 Source 到 TargetDTO,自动处理嵌套对象 Address
@Mapping(source = "name", target = "fullName")
TargetDTO convertToDto(Source source);

// 映射 Address 到 AddressDTO
AddressDTO addressToAddressDTO(Address address);
}

调用

1
2
3
4
5
6
7
8
9
10
11
12
13

Address address = new Address();
address.setStreet("Main Street");
address.setCity("Springfield");

Source source = new Source();
source.setName("Tom");
source.setAddress(address);

TargetDTO dto = SourceToDtoMapper.INSTANCE.convertToDto(source);
System.out.println(dto.getAddress().getStreet()); // 输出 "Main Street"
System.out.println(dto.getAddress().getCity()); // 输出 "Springfield"

集合映射

MapStruct还支持集合类型(如 List,Set)的映射。它会自动将源代码中的集合映射到目标对象中的集合

示例

假设 SourceTargetDTO 包含一个 List<Address>,MapStruct 会自动映射这些集合中的每一个元素。

1
2
3
4
5
6
7
8
9
10
public class Source {
private List<Address> addresses;
// getters and setters
}

public class TargetDTO {
private List<AddressDTO> addresses;
// getters and setters
}

无需额外配置,MapStruct 会自动处理 List<Address>List<AddressDTO> 的映射。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Mapper
public interface SourceToDtoMapper {

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

// 自动处理 List<Address> 到 List<AddressDTO> 的映射
SourceToDtoMapper INSTANCE = Mappers.getMapper(SourceToDtoMapper.class);

@Mapping(source = "name", target = "fullName")
TargetDTO convertToDto(Source source);

AddressDTO addressToAddressDTO(Address address);
}

嵌套字段的自定义映射

可以通过 @Mapping 注解映射嵌套对象的特定字段。

示例

假设 Source 中的 Address 对象的 city 字段需要映射到 TargetDTO 中的 location 字段。

1
2
3
4
5
6
public class TargetDTO {
private String fullName;
private String location;
// getters and setters
}

1
2
3
4
5
6
7
8
9
10
@Mapper
public interface SourceToDtoMapper {

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

@Mapping(source = "name", target = "fullName")
@Mapping(source = "address.city", target = "location") // 映射嵌套对象字段
TargetDTO convertToDto(Source source);
}

调用

1
2
3
4
5
6
7
8
9
Source source = new Source();
source.setName("Tom");
Address address = new Address();
address.setCity("Springfield");
source.setAddress(address);

TargetDTO dto = SourceToDtoMapper.INSTANCE.convertToDto(source);
System.out.println(dto.getLocation()); // 输出 "Springfield"

多个源对象映射到一个目标对象

MapStruct 支持将多个源对象映射到一个目标对象,使用 @Mapping 注解可以指定来自不同源对象的字段。

示例

假设你有两个源对象 PersonAddress,你希望将它们的属性合并到 PersonDTO 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Person {
private String firstName;
private String lastName;
// getters and setters
}

public class Address {
private String city;
private String street;
// getters and setters
}

public class PersonDTO {
private String fullName;
private String city;
// getters and setters
}

Mapper中通过 @Mapping 注解指定字段的来源,并通过参数顺序来处理多个源对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Mapper
public interface PersonMapper {

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

@Mapping(source = "person.firstName", target = "fullName", qualifiedByName = "concatNames")
@Mapping(source = "address.city", target = "city")
PersonDTO toPersonDto(Person person, Address address);

// 自定义方法来合并姓名
default String concatNames(String firstName, String lastName) {
return firstName + " " + lastName;
}
}

调用

1
2
3
4
5
6
7
8
9
10
11
Person person = new Person();
person.setFirstName("John");
person.setLastName("Doe");

Address address = new Address();
address.setCity("New York");

PersonDTO dto = PersonMapper.INSTANCE.toPersonDto(person, address);
System.out.println(dto.getFullName()); // 输出 "John Doe"
System.out.println(dto.getCity()); // 输出 "New York"

总结:

  • 自定义映射:使用 @Mapping 注解可以轻松自定义字段映射,支持字段名不同、字段类型不同的场景。
  • 嵌套对象映射:MapStruct 支持嵌套对象自动映射,也支持自定义映射规则。
  • 集合映射:MapStruct 能自动处理集合的映射,确保每个元素被正确映射。
  • 多个源对象映射:可以轻松将多个源对象合并到一个目标对象。