二次查询父子表并组装
这里所说的父子表指一种一对多关系,如班级-学生。定义班级为父表,学生为子表。
将表数据转换为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代码有三种实现方式:
- 双重for循环
for(father in fathers) {
for(child in children) {
if(child.parentId == father.id) {
faher.children.add(child)
}
}
}
- lamda表达式形式的双重循环
fathers.each(father->{
father.children.addAll(children.stream().filter(c -> c.parentId == father.id).collect())
})
- 两次循环
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
- 循环使用findById查询数据库
无论是jpa还是MyBatis对findById都有很好的缓存。如下实现即可:
@Transaction(readonly)
List children = childRepo.findSomeChildren()
for(child in children){
Father father = fatherRepo.findById(child.parentId)
child.setFather(father)
}
- 使用findByIdIn避免循环查询数据库
类比子表对象挂载至父表对象的思路,使用findByIdIn。避免循环查询数据库。
由于MyBatis和jpa等框架的缓存,方法1在一般情况下循环查询数据库的次数不会太多。编码实现简单,一般选择方法1的实现。
方法2避免的循环查询数据库,编码实现比方法1复杂。