1. 概述
本文将使用 MapStruct 库 演示如何将源 POJO 的嵌套属性映射到目标 POJO 的属性中。这种技术同样适用于将源实体属性值映射到目标实体的嵌套属性值。
MapStruct 使用注解定义对象属性间的映射关系,其 Maven 插件会根据注解元数据自动生成映射工具类。此外,该库还支持自定义映射工具类以实现精细化控制。
我们将通过实战演示如何使用 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
类型的属性:
public class Address {
private String city;
private String zipCode;
//..标准 getter/setter
}
目标实体 OrderDto
是 Order
的扁平化简化版本,包含 customerName
、customerCity
、customerZipCode
、productName
和 productPrice
等字段:
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
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#customer
和Order#product
) - ✅ 支持 Java 表达式(如
customerZipCode
的映射)
编译后,MapStruct 插件会生成 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();
}
//..其他私有方法
}
⚠️ 关键差异:
- 常规映射会生成带空值检查的私有方法
- 表达式映射(如
customerZipCode
)直接使用原始代码,无空值检查!
测试映射效果:
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());
}
4. 使用抽象映射器实现嵌套映射
当 @Mapping
注解无法满足复杂映射需求时,可采用抽象映射器类:
@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()
可实现自定义初始化逻辑- 抽象方法
mapCustomer()
和mapProduct()
处理具体映射 - ✅ 灵活选择抽象/具体方法实现定制化
- ✅ 支持
@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();
}
//..其他生成的私有方法
}
测试抽象映射器:
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());
}
5. 总结
本文深入探讨了 MapStruct 的嵌套属性映射技术,这是数据转换应用中的常见需求。MapStruct 通过以下特性解决了这类问题:
- ✅ 点表示法简化嵌套属性访问
- ✅ 表达式映射支持特殊场景
- ✅ 抽象映射器提供灵活定制能力
- ✅ 自动生成代码保证运行时性能
MapStruct 的精细化控制能力使其成为处理复杂映射场景的强大工具。掌握这些技术后,可以更优雅地应对企业级开发中的数据转换需求。
完整示例代码请访问 GitHub 仓库。