返回

MapStruct 数组映射:如何解决新列表值为空的问题?

java

MapStruct 数组映射问题:为何新列表值全部为空?

你正在使用 MapStruct 简化 Java 对象之间的映射,却发现处理列表时遇到了难题?目标列表创建成功,字段映射看似正确,但所有值却都是空荡荡的?别担心,你并不是唯一一个掉入这个“陷阱”的开发者。本文将带你深入剖析这一问题,并提供清晰易懂的解决方案,助你轻松化解 MapStruct 数组映射的常见障碍。

直击问题根源:从一个例子说起

假设你需要将 OrderDto 对象列表转换为 OrderEntity 对象列表,两者结构类似,但 OrderDto 中的商品信息存储在 OrderItemDto 列表中,而 OrderEntity 需要直接映射 OrderItemDto 中的字段。

你可能已经编写了如下代码:

public static void main(String[] args) {
    // 创建 OrderItemDto 对象
    OrderItemDto orderItemDto = new OrderItemDto();
    orderItemDto.setProductName("Java 编程思想");
    orderItemDto.setQuantity(2);

    // 创建 OrderDto 对象
    OrderDto orderDto = new OrderDto();
    orderDto.setOrderId("123456");
    orderDto.setItems(List.of(orderItemDto));

    // 创建 OrderDto 列表
    List<OrderDto> orderDtos = new ArrayList<>();
    orderDtos.add(orderDto);

    // 创建 OrderMapper 实例
    OrderMapper mapper = Mappers.getMapper(OrderMapper.class);

    // 映射 OrderDto 列表到 OrderEntity 列表
    List<OrderEntity> orderEntities = orderDtos.stream().map(mapper::toEntity).toList();

    // 打印结果,你会发现 orderEntities 列表中的对象拥有正确的结构,
    // 但 productName 和 quantity 的值却是 null
    System.out.println(orderEntities); 
}

// 定义 OrderMapper 接口
@Mapper
public interface OrderMapper {
    OrderEntity toEntity(OrderDto orderDto);
}

// 定义 OrderItemDto 类
public class OrderItemDto {
    private String productName;
    private int quantity;
    // 省略 getter 和 setter
}

// 定义 OrderDto 类
public class OrderDto {
    private String orderId;
    private List<OrderItemDto> items;
    // 省略 getter 和 setter
}

// 定义 OrderEntity 类
public class OrderEntity {
    private String orderId;
    private String productName;
    private int quantity;
    // 省略 getter 和 setter
}

运行这段代码,你会惊讶地发现,orderEntities 列表中的 OrderEntity 对象虽然结构正确,但 productNamequantity 的值却是 null。难道 MapStruct 失效了?

其实不然,问题在于我们没有明确告知 MapStruct 如何处理 OrderItemDto 列表中的字段。默认情况下,MapStruct 只会映射相同名称的字段。

拨开迷雾:精准配置映射关系

为了解决这个问题,我们需要“牵线搭桥”,明确告诉 MapStruct OrderItemDto 中的字段应该映射到 OrderEntity 的哪些字段。

更新后的 OrderMapper 接口如下所示:

@Mapper
public interface OrderMapper {

    @Mapping(target = "productName", source = "items.get(0).productName")
    @Mapping(target = "quantity", source = "items.get(0).quantity")
    OrderEntity toEntity(OrderDto orderDto);
}

通过添加 @Mapping 注解,我们清晰地指示 MapStruct 将 OrderEntityproductName 字段映射到 OrderDtoitems 列表第一个元素的 productName 字段,quantity 字段同理。

完成上述修改后,再次运行代码,你会欣喜地看到 orderEntities 列表中的对象拥有了正确的值:

[
  {
    "orderId": "123456",
    "productName": "Java 编程思想",
    "quantity": 2
  }
]

举一反三:应对更复杂的嵌套场景

如果 OrderDto 中包含多个 OrderItemDto 对象,我们需要迭代 items 列表,为每个 OrderItemDto 创建对应的 OrderEntity

@Mapper
public interface OrderMapper {

    List<OrderEntity> toEntityList(List<OrderDto> orderDtos);

    @Mapping(target = "productName", source = "item.productName")
    @Mapping(target = "quantity", source = "item.quantity")
    OrderEntity toEntity(OrderDto orderDto, @Context OrderItemDto item);
}

然后修改代码调用 toEntityList 方法:

List<OrderEntity> orderEntities = mapper.toEntityList(orderDtos);

总结:掌握 MapStruct 数组映射的精髓

MapStruct 就像一位高效的“翻译官”,需要开发者提供清晰的“翻译规则”才能准确地完成对象转换。当遇到映射后的对象属性值为空的情况时,切记检查是否正确配置了所有字段的映射关系,尤其是嵌套对象的字段。

希望本文能帮助你解决 MapStruct 数组映射过程中遇到的问题,让你在 Java 开发的道路上更加游刃有余!

常见问题解答

1. 为什么 MapStruct 不能自动映射嵌套对象的字段?

MapStruct 默认情况下只会映射相同名称的字段,对于嵌套对象,它无法自动识别和映射内部字段。

2. 如何映射嵌套对象中的深层字段?

可以使用.操作符来访问嵌套对象中的深层字段,例如:@Mapping(target = "address.city", source = "user.address.city")

3. 可以使用表达式来定义映射关系吗?

可以,MapStruct 支持使用表达式语言(SpEL)来定义更复杂的映射关系,例如:@Mapping(target = "fullName", expression = "java(source.getFirstName() + ' ' + source.getLastName())")

4. 如何处理源对象和目标对象字段名称不同的情况?

可以使用 @Mapping 注解的 sourcetarget 属性来指定不同的字段名称。

5. 如果嵌套对象是集合类型,如何进行映射?

可以使用 @IterableMapping 注解来指定集合类型属性的映射关系。