1. 概述

本文将使用 MapStruct 库 演示如何将源 POJO 的嵌套属性映射到目标 POJO 的属性中。这种技术同样适用于将源实体属性值映射到目标实体的嵌套属性值。

MapStruct 使用注解定义对象属性间的映射关系,其 Maven 插件会根据注解元数据自动生成映射工具类。此外,该库还支持自定义映射工具类以实现精细化控制。

我们将通过实战演示如何使用 MapStruct 将分层源实体的嵌套属性映射到扁平化的目标实体中。

2. 应用场景

考虑源实体 Order 和目标实体 OrderDto,展示 MapStruct 的嵌套映射能力:

nested mapping cld e1748024577221 300x264

源实体 Order 是包含嵌套结构的复杂对象,包含 CustomerProduct

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
}

目标实体 OrderDtoOrder 的扁平化简化版本,包含 customerNamecustomerCitycustomerZipCodeproductNameproductPrice 等字段:

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() 方法实现 OrderOrderDto 的转换
  • @Mapping 注解定义源对象(含嵌套属性)到目标属性的映射关系
  • ✅ 使用点表示法访问嵌套属性(如 Order#customerOrder#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 通过以下特性解决了这类问题:

  1. 点表示法简化嵌套属性访问
  2. 表达式映射支持特殊场景
  3. 抽象映射器提供灵活定制能力
  4. 自动生成代码保证运行时性能

MapStruct 的精细化控制能力使其成为处理复杂映射场景的强大工具。掌握这些技术后,可以更优雅地应对企业级开发中的数据转换需求。

完整示例代码请访问 GitHub 仓库


原始标题:How to Do Nested Mapping in Mapstruct? | Baeldung