Skip to content

示例:如何级联填充

本示例将指导你如何指定填充的顺序,从而实现级联填充的效果。在这之前,请先确保你已经阅读过快速开始,并且成功将 crane4j 引入你的项目。

1.指定填充顺序

我们假设现在有一个员工对象,我们需要填充其所属的一级、二级与三级部门的信息。不过,我们数据库中只存储了三级部门 ID,此时,我们需要先查询三级部门的信息,然后填充该部门归属的二级部门 ID……依次类推。

则此时,如果我们没有一个根据三级部门 ID 查询全部关联上级部门的接口,而只有分别根据某一级部门 ID 查询的接口,那么我们可以通过指定填充顺序来实现级联填充的效果:

java
public Emp {
  
    @Assemble(
      	container = "dept", 
      	prop = { "name:firstDeptName" },
      	sort = 3 // 指定顺序
    )
  	private Integer firstIdDeptId;
  	private String firstDeptName;
  
  	@Assemble(
      	container = "dept", 
      	prop = { "name:secondDeptName", "parentDeptId:firstIdDeptId"},
      	sort = 2 // 指定顺序
    )
  	private Integer secondDeptId;
  	private String secondDeptName;
  
  	@Assemble(
      	container = "dept", 
      	prop = { "name:thirdDeptName", "parentDeptId:secondDeptId"},
      	sort = 1 // 指定顺序
    )
  	private Integer thirdDeptId;
  	private String thirdDeptName;
}

如果你使用的是基于 AOP 的自动填充,那么你可以在 @AutoOperate 注解中指定:

java
// 显式指定操作执行器
@AutoOperate(type = Student.class, executorType = OrderedBeanOperationExecutor.class)
public List<Student> listStudent(List<Integer> ids) {
    // do something
}

TIP

关于如何配置填充顺序的具体内容,请参见:指定操作顺序

2.在数据源层面处理级联关系

指定顺序执行器会导致该对象中所有的操作都变为同步的,即使两个不同的操作都来自相同的数据源,它们依然会分开成两个步骤分别查库。如果你觉得这一步比较消耗性能,或者有其他的顾虑,你也可以直接在数据源的层面直接就处理好级联关系。

这里我们以方法数据源容器为例,我们可以直接提供一个方法数据源容器,它从一开始就支持通过下级部门 ID 获取所有关联的上级部门:

java
@Component
@RequiredArgsConstructor
public class DeptService {
  	private final DeptMapper deptMapper;
  
  	@ContainerMethod(
    		container = "thirdDept", resultType = Map.class, resultKey = "thirdDeptId"
    )
  	public List<Map<String, Object>> queryDeptWithParentByThirdDept(List<Integer> thirdDeptIds) {
      	// 查询一级部门,并按 ID 分组
      	Map<Interger, Dept> thirdDepts = deptMapper.listByIds(thirdDeptIds).stream()
          .collect(Collectors.toMap(Dept::getId, dept -> dept));
      
      	// 查询二级部门,并按 ID 分组
      	Set<Interger> secondDeptIds = thirdDepts.values().stream()
          	.map(Dept::getParentId)
          	.collect(Collector.toSet());
      	Map<Interger, Dept> secondDepts = deptMapper.listByIds(secondDeptIds).stream()
          .collect(Collectors.toMap(Dept::getId, dept -> dept));
      
      	// 查询一级部门,并按 ID 分组
      	Set<Interger> firstDeptIds = secondDeptIds.values().stream()
          	.map(Dept::getParentId)
          	.collect(Collector.toSet());
      	Map<Interger, Dept> firstDepts = deptMapper.listByIds(firstDeptIds).stream()
          .collect(Collectors.toMap(Dept::getId, dept -> dept));
      
      	// 组装数据
      	List<Map<String, String>> results = new ArrayLsit<>(thirdDepts.size());
      	thirdDepts.values().forEach(td -> {
          	Dept sd = secondDepts.get(td.getParentId());
          	Dept fd = firstDepts.get(sd.getParentIds());
          
          	Map<String, Object> r = new HashMap<>(6);
          	r.put("thirdDeptId", r.getId());
          	r.put("thirdDeptName", r.getName());
          	r.put("secondDeptId", sd.getId());
          	r.put("secondDeptName", sd.getName());
          	r.put("firstIdDeptId", fd.getId());
          	r.put("firstIdDeptName", fd.getName());
          	results.put(r);
        });
        return results;
    } 
}

随后,我们在部门对象上直接引用相关属性即可:

java
public Emp {
  	private Integer firstIdDeptId;
  	private String firstDeptName;
  	private Integer secondDeptId;
  	private String secondDeptName;
  	@Assemble(
      	container = "thirdDept", 
      	prop = { 
          "thirdDeptName", 
          "secondDeptId", "secondDeptName",
          "firstIdDeptId", "firstIdDeptName"
        },
    )
  	private Integer thirdDeptId;
  	private String thirdDeptName;
}

TIP

关于方法容器的使用和配置,请参见 方法容器 一节。