二次查询父子表并组装

豆苗稀 / 2023-07-06 / 原文

这里所说的父子表指一种一对多关系,如班级-学生。定义班级为父表,学生为子表。
将表数据转换为java的运行时数据

一、子表对象挂载至父表对象

表clazz: id
表student: id, clazzId

类clazz: id, students
类studnet: id, clazzId

二次查询数据库,获取父表和子表的相关数据

List fathers = fatherRepo.findSomeList();
List fatherIds = fathers.stream().map(Father::getId).collect(toList);
List children = childRepo.findByFatherIdsIn(fatherIds);

注:oracle的in字句限制1000。MySQL限制了SQL语句的最大长度,可以认为对in语句没有限制。sqlserver对in语句没有限制。

子表数据挂载到父表对象的属性上

二次查询数据库获取父子表数据后,需要在java代码中,将子表数据挂载到父表对象的属性上。

java代码有三种实现方式:

  1. 双重for循环
for(father in fathers) {
  for(child in children) {
    if(child.parentId == father.id) {
      faher.children.add(child)
    }
  }
}
  1. lamda表达式形式的双重循环
fathers.each(father->{
  father.children.addAll(children.stream().filter(c -> c.parentId == father.id).collect())
})
  1. 两次循环
Map<ID,List<Child>> collect = children.stream().collect(Collectors.groupingBy(Child::getParentId))
fathers.each(father->{
  father.children.addAll(collect.getOrDefault(father.id, List.of()))
})

理论上方法3的两次循环时间复杂度为2n,方法1和2的时间复杂度为n^2. 但方法3需要引入多余的外部变量,编码不太方便。
方法2的编码方式最简洁。但据说lamda形式的循环略慢于普通for循环,因此执行速度可能略慢于方法1。
方法1的编码方式最灵活,方便增减其他逻辑代码。另外,不存在方法2和3中,有单行代码过长的问题。
在实际的javaweb开发中,优先使用方法1,其次2,再次3

二、父表对象挂载至子表对象

表clazz: id
表student: id, clazzId

类clazz: id
类student: id, clazz

  1. 循环使用findById查询数据库

无论是jpa还是MyBatis对findById都有很好的缓存。如下实现即可:

@Transaction(readonly)
List children = childRepo.findSomeChildren()
for(child in children){
  Father father = fatherRepo.findById(child.parentId)
  child.setFather(father)
}
  1. 使用findByIdIn避免循环查询数据库
    类比子表对象挂载至父表对象的思路,使用findByIdIn。避免循环查询数据库。

由于MyBatis和jpa等框架的缓存,方法1在一般情况下循环查询数据库的次数不会太多。编码实现简单,一般选择方法1的实现。
方法2避免的循环查询数据库,编码实现比方法1复杂。