Skip to content

示例: 如何填充一个复杂对象

你了解了如何基于自动填充,在 Controller 方法返回后进行简单的填充。

在本文的例子中,你可以结合一个填充起来比较麻烦的的订单列表查询的例子,进一步了解:

  • 选项式风格配置与组合式风格配置的使用方式与差异
  • 如何使用组合注解抽离并简化复杂配置

1.原始代码

我们先给出不使用 crane4j 前的原始代码,后续将会围绕其作出修改。

1.1.实体类与数据结构

我们有实体类 OrderItem 如下:

java
@Data // 使用 lombok 生成 getter/setter 方法
public class Order {
    private Integer id;
    private String orderType;
    private Integer customerId;
    private String customerName;
    private String customerType;
    private List<Item> items;
}

@Data // 使用 lombok 生成 getter/setter 方法
public class Item {
    private Integer id;
    private String name;
    private String type;
}

不过在最开始的时候我们仅有少量的必要信息:

json
{
    "id": 123,
    "orderType": "new_order",
    "customerId": 123,
    "items": [
        {
            "id": 1
        },
        {
            "id": 2
        }
    ]
}

其他内容都需要通过关联查询获取。

1.2.实现代码

由于查询到所需的订单数据后,我们还需要关联查询出其他的信息,因此该接口实行如下:

java
public List<Oder> listOrder(List<Integer> ids) {
    // 查询订单列表
    List<Order> orders = orderService.selectList(ids);

    // 1、填充订单类型
    Map<String, OrderType> orderTypeMap = Stream.of(OrderType.values())
        .collect(Collectors.toMap(OrderType::getCode, e -> e));
    orders.forEach(order -> {
        String orderTypeCode = foo.getOrderType(); // 根据订单类型编码获取对应的名称
        OrderType orderType = orderTypeMap.get(orderTypeCode);
        if (Objects.nonNull(orderType)) {
            foo.setOrderType(orderType.getName());
        }
    });

    // 2、填充关联客户信息
    Set<Integer> customerIds = orders.stream()
        .map(Order::getCustomerId)
        .collect(Collectors.toList());
    List<Customer> customers = customerService.selectListByIds(customerIds);
    Map<Integer, Customer> customerMap = customers.stream()
        .collect(Collectors.toMap(Customer::getId, e -> e));
    orders.forEach(order -> {
        Integer customerId = foo.getCustomerId(); // 根据客户Id获取对应的客户名称、客户类型
        Customer customer = orderTypeMap.get(customerId);
        if (Objects.nonNull(customer)) {
            foo.setCustomerName(customer.getName());
            foo.setCustomerType(customer.getType());
        }
    });

    // 3、填充关联商品信息
    Set<Integer> itemIds = orders.stream()
        .map(Order::getItems)
        .flatMap(Collection::stream)
        .map(Item::getId)
        .collect(Collectors.toList());
    List<Item> items = itemService.selectListByIds(itemIds); // 查询商品信息,并按 id 分组
    Map<Integer, Item> itemMap = items.stream()
        .collect(Collectors.toMap(Item::getId, e -> e));
    orders.forEach(order -> {
        List<Item> itemsOfOrder = order.getItems();
        itemsOfOrder.forEach(itemOfOrder -> { // 遍历订单中的管理商品,并找到对应的商品信息
            Item item = itemMap.get(itemOfOrder.getId());
            if (Objects.nonNull(item)) {
                itemOfOrder.setName(item.getName());
                itemOfOrder.setType(item.getType());
            }
        });
    });
}

上面代码用于为 Order 对象填充订单类型、关联客户与关联商品三类信息,实际场景中需要的可能数据远远不止这些,此处已经做了简化,不过依然显得十分繁琐。

2.使用选项式风格配置

你可以基于选项式风格的配置,通过 crane4j 更优雅地完成上述字段填充逻辑:

java
@Data
public class Order {
    private Integer id;

    // 将订单类型编码转为订单类型值
    @AssembleEnum(
        type = OrderType.class, enumKey = "code", // 填充数据源为 OrderType 枚举
        props = @Mapping(src = "name") // OrderType.name -> Order.orderType
    )
    private String orderType;


    // 2、填充关联客户信息
    @AssembleMethod(
        targetType = CustomerService.class, // 填充数据源为 CustomerService#listByIds 方法
        method = @ContainerMethod(bindMethod = "listByIds", resultType = Customer.class),  
        prop = {
            "name:customerName", // Customer.name -> Order.customerName
            "type:customerType" // Customer.type -> Order.customerType
        }
    )
    private Integer customerId;
    private String customerName;
    private String customerType;
    
    @Disassemble(type = Item.class) // 嵌套填充商品信息
    private List<Item> items;
}

@Data
public class Item {
    
    // 3、填充关联商品信息
    @AssembleMethod(
        targetType = ItemService.class, // 填充数据源为 CustomerService#listByIds 方法
        method = @ContainerMethod(bindMethod = "listByIds", resultType = Item.class),
        prop = {
            "name", // Item.name -> Item.name
            "type" // Item.type -> Item.type
        },
        handlerType = ManyToManyAssembleOperationHandler.class // 多对多
    )
    private Integer id;
    private String name;
    private String type;
}

而原来的代码仅需保留核心业务逻辑即可:

java
@AutoOperate
public List<Oder> listOrder(List<Integer> ids) {
    return orderService.selectList(ids);
}

3.使用组合式风格配置

而如果要使用组合式风格的配置,则在第一步需要先配置数据源

java
@ContainerEnum(namespace = "order_type", enumKey = "code")
public enum OrderType {}

@ContainerMethod(namespace = "customer", bindMethod = "listByIds", resultType = Customer.class)
public interface CustomerService {
    List<Customer> listByIds(Collection<Integer> ids);
}

@ContainerMethod(namespace = "item", bindMethod = "listByIds", resultType = Item.class)
public interface ItemService {
    List<Item> listByIds(Collection<Integer> ids);
}

然后再在类属性上统一使用 @Assemble 注解引用数据源,将它们组合到一起:

java
@Data
public class Order {

    // 将订单类型编码转为订单类型值
    @Assemble(
        namespace = "order_type", 
        props = @Mapping(src = "name") // OrderType.name -> Order.orderType
    )
    private String orderType;


    // 2、填充关联客户信息
    @Assemble(
        namesapce = "customer", 
        props = {
            @Mapping(src = "name", ref = "customerName"), // Customer.name -> Order.customerName
            @Mapping(src = "type", ref = "customerType") // Customer.type -> Order.customerType
        }
    )
    private Integer customerId;
    private String customerName;
    private String customerType;

    @Disassemble(type = Item.class) // 嵌套填充商品信息
    private List<Item> items;
}

@Data
public class Item {

    // 3、填充关联商品信息
    @Assemble(
        namespace = "item", 
        prop = {
            "name", // Item.name -> Item.name
            "type" // Item.type -> Item.type
        },
        handlerType = ManyToManyAssembleOperationHandler.class
    )
    private Integer id;
    private String name;
    private String type;
}

4.使用组合注解简化配置

在上述两种风格的基础上,我们还可以进一步通过组合组件简化代码,我们以组合式风格的注解为例:

java
// 将订单类型编码转为订单类型值
@Assemble(
    namespace = "order_type", 
    props = @Mapping(src = "name", ref = "orderType") // OrderType.name -> Order.orderType
)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AssembleOrderType { }

// 填充关联客户信息
@Assemble(
    namesapce = "customer", 
    props = {
        @Mapping(src = "name", ref = "customerName"), // Customer.name -> Order.customerName
        @Mapping(src = "type", ref = "customerType") // Customer.type -> Order.customerType
    }
)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AssembleCustomer { }

// 填充关联商品信息
@Assemble(
    namespace = "item", 
    props = {
        @Mapping("name"), // Item.name -> Item.name
        @Mapping("type") // Item.type -> Item.type
    },
    handlerType = ManyToManyAssembleOperationHandler.class
)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AssembleItem { }

在将原本的注解封装为一个新组合注解后,我们可以直接使用组合注解

java
@Data
public class Order {

    // 将订单类型编码转为订单类型值
    @AssembleOrderType
    private Integer orderTypeCode;
    private String orderTypeName;


    // 2、填充关联客户信息
    @AssembleCustomer
    private Integer customerId;
    private String customerName;
    private String customerType;

    // 嵌套填充商品信息
    @Disassemble(type = Item.class)
    private List<Item> items;
}

@Data
public class Item {

    // 3、填充关联商品信息
    @AssembleItem
    private Integer id;
    private String name;
    private String type;
}