如何在 Mapstruct 中进行嵌套映射?
1、概览
MapStruct
通过注解来定义 POJO 属性间的映射关系。其 Maven 插件会读取注解中定义的元数据,自动生成 Mapper
工具类。此外,它还支持通过自定义映射工具实现细粒度控制。
本文将带你了解如何用 MapStruct 将层次化的源实体嵌套属性映射到扁平化的目标实体
2、用例
类体系图如下。通过源实体 Order
和目标实体 OrderDto
来演示 MapStruct 库执行嵌套映射的能力:
源实体 Order
表示具有嵌套结构的复杂对象,包含 Customer
和 Product
:
public class Order {
private Customer customer;
private Product product;
// Getter/Setter 省略
}
public class Customer {
private String name;
private Address address;
// Getter/Setter 省略
}
public class Product {
private String name;
private double price;
// Getter/Setter 省略
}
此外,Customer
实体具有一个类型为 Address
的 address
属性:
public class Address {
private String city;
private String zipCode;
// Getter/Setter 省略
}
目标实体 OrderDto
是 Order
实体的简化扁平化版本。 它包含诸如 customerName
、customerCity
、customerZipCode
、productName
和 productPrice
等字段,这些字段均来自 Order
实体的嵌套结构:
public class OrderDto {
private String customerName;
private String customerCity;
private String customerZipCode;
private String productName;
private double productPrice;
// Getter/Setter 省略
}
接下来看看如何使用 MapStruct 库从 Order
对象创建 OrderDto
。
3、使用 @Mapping 注解
仅需一对一映射且无需定制化处理的场景,直接使用 @Mapping
注解即可实现。
首先,根据 MapStruct 库的规范,我们需要定义带有 @Mapper
注解的 Mapper 接口:
@Mapper
public interface OrderMapper {
OrderMapper INSTANCE = Mappers.getMapper(OrderMapper.class);
@Mapping(source = "customer.name", target = "customerName")
@Mapping(source = "product.name", target = "productName")
@Mapping(source = "product.price", target = "productPrice")
@Mapping(source = "customer.address.city", target = "customerCity")
@Mapping(expression = "java(order.getCustomer().getAddress().getZipCode())",
target = "customerZipCode")
OrderDto orderToOrderDto(Order order);
}
在 OrderMapper
接口中,orderToOrderDto()
方法将 Order
对象转换为 OrderDto
对象。该方法上的 @Mapping
注解用于定义从 Order
对象属性(包括嵌套属性)到目标 OrderDto
属性的映射关系。
此外,我们通过 .
导航访问 Order#customer
和 Order#product
等嵌套属性。同理,也通过此方式访问 Customer#address
的 city
属性。同时,我们还可自由使用 Java 表达式,例如这里通过表达式填充了 OrderDto#customerZipCode
属性。
最终,当我们构建代码时,Maven 插件 MapStruct Processor 会生成 OrderMapperImpl
类:
public class OrderMapperImpl implements OrderMapper {
@Override
public OrderDto orderToOrderDto(Order order) {
if ( order == null ) {
return null;
}
OrderDto orderDto = new OrderDto();
orderDto.setCustomerName( orderCustomerName( order ) );
orderDto.setProductName( orderProductName( order ) );
orderDto.setProductPrice( orderProductPrice( order ) );
orderDto.setCustomerCity( orderCustomerAddressCity( order ) );
orderDto.setCustomerZipCode( order.getCustomer().getAddress().getZipCode() );
return orderDto;
}
private String orderCustomerName(Order order) {
Customer customer = order.getCustomer();
if ( customer == null ) {
return null;
}
return customer.getName();
}
private String orderCustomerAddressCity(Order order) {
Customer customer = order.getCustomer();
if ( customer == null ) {
return null;
}
Address address = customer.getAddress();
if ( address == null ) {
return null;
}
return address.getCity();
}
//。。。其他私有方法
}
Maven 插件会解析 @Mapping
注解,并智能生成 orderToOrderDto()
方法及相关的私有工具方法,用于提取 Order
对象的嵌套属性。但填充 OrderDto#customerZipCode
属性时,直接使用了注解中的 Java 表达式。与其他场景不同,此处未生成任何 null
值检查逻辑。
接下来,调用生成的 Mapper 类:
void givenOrder_whenMapToOrderDto_thenMapNestedAttributes() {
Order order = createSampleOrderObject();
OrderDto orderDto = OrderMapper.INSTANCE.orderToOrderDto(order);
assertEquals("John Doe", orderDto.getCustomerName());
assertEquals("New York", orderDto.getCustomerCity());
assertEquals("10001", orderDto.getCustomerZipCode());
assertEquals("Laptop", orderDto.getProductName());
assertEquals(1200.00, orderDto.getProductPrice());
}
createSampleOrder()
方法用于创建示例 Order
对象,该对象作为参数传递给 Mapper 的 orderToOrderDto()
方法。最终,通过断言来验证所有嵌套属性均已正确映射到目标 OrderDto
对象。
4、使用抽象 Mapper 实现
当 @Mapping
注解无法满足映射过程中的灵活需求时,可通过抽象 Mapper 类实现:
@Mapper
public abstract class AbstractOrderMapper {
public static final AbstractOrderMapper INSTANCE = Mappers.getMapper(AbstractOrderMapper.class);
public OrderDto orderToOrderDto(Order order) {
OrderDto orderDto = applyCustomMappings(order);
orderDto = mapCustomer(order);
mapProduct(order, orderDto);
return orderDto;
}
@Mapping(source = "customer.name", target = "customerName")
@Mapping(source = "customer.address.city", target = "customerCity")
@Mapping(source = "customer.address.zipCode", target = "customerZipCode")
protected abstract OrderDto mapCustomer(Order order);
@Mapping(source = "product.name", target = "productName")
@Mapping(source = "product.price", target = "productPrice")
protected abstract void mapProduct(Order order, @MappingTarget OrderDto orderDto);
}
首先,orderToOrderDto()
方法会调用 applyCustomMappings()
方法来初始化 OrderDto
对象。此外,applyCustomMappings()
可实现特定逻辑或调用下游服务来创建 OrderDto
对象。接着,我们会调用如 mapCustomer()
和 mapProduct()
等抽象方法。这些抽象方法带有与之前章节讨论过的基本 @Mapping
注解,用于从 Order
对象更新 OrderDto
对象中 customer
和 product
属性的值。
在另一种场景中,我们可以选择将哪些方法保留为抽象方法,哪些方法具体实现以进行定制化。此外,我们还可以使用其他 MapStruct 功能或注解(例如 @ObjectFactory
)来实现需要精细控制的定制化逻辑。
接下来,构建该类以生成 AbstractOrderMapperImpl
类:
public class AbstractOrderMapperImpl extends AbstractOrderMapper {
@Override
protected OrderDto mapCustomer(Order order) {
if ( order == null ) {
return null;
}
OrderDto orderDto = new OrderDto();
orderDto.setCustomerName( orderCustomerName( order ) );
orderDto.setCustomerCity( orderCustomerAddressCity( order ) );
orderDto.setCustomerZipCode( orderCustomerAddressZipCode( order ) );
return orderDto;
}
@Override
protected void mapProduct(Order order, OrderDto orderDto) {
if ( order == null ) {
return;
}
orderDto.setProductName( orderProductName( order ) );
orderDto.setProductPrice( orderProductPrice( order ) );
}
private String orderCustomerName(Order order) {
Customer customer = order.getCustomer();
if ( customer == null ) {
return null;
}
return customer.getName();
}
// 其他生成的私有方法,用于从 Order 对象的嵌套属性中提取属性值
}
Maven 插件解析了 mapCustomer()
和 mapProduct()
方法上的 @Mapping
注解元数据,并生成了它们的实现。继承自父类 AbstractOrderMapper
的 AbstractOrderMapperImpl#orderToOrderDto()
方法会调用这些已实现的方法。
现在,可以运行 AbstractOrderMapper#orderToOrderDto()
方法并验证映射结果:
void givenOrder_whenMapToOrderDto_thenMapNestedAttributesWithAbstractMapper() {
Order order = createSampleOrderObject();
OrderDto orderDto = AbstractOrderMapper.INSTANCE.orderToOrderDto(order);
assertEquals("John Doe", orderDto.getCustomerName());
assertEquals("New York", orderDto.getCustomerCity());
assertEquals("10001", orderDto.getCustomerZipCode());
assertEquals("Laptop", orderDto.getProductName());
assertEquals(1200.00, orderDto.getProductPrice());
}
首先,我们调用 createSampleOrderObject()
方法创建示例 Order
对象。接着实例化 AbstractOrderMapper
的实现类。随后将示例 Order
对象传递给 AbstractOrderMapper#orderToOrderDto()
方法。最终验证 OrderDto
对象各属性的值。
5、总结
本文介绍了如何通过 MapStruct 将源实体的嵌套属性映射到目标实体。
Ref:https://www.baeldung.com/mapstruct-nested-mapping