对于一个查询来说,如果数据量是不断增加的,并且对于最高权限要显示全表数据,就必须考虑真分页了,那么树形结构如何真分页呢?网上找了好多,并没有一个具体的方案,只能自己想一个了真分页: 即每次只从数据库取到相应的数据,然后返回,这样可以性能要比假分页高一些
假分页: 即查询数据库中符合条件的所有数据,然后在模型层,自己对list结果进行处理为相应的分页效果,或者由web进行分页展示
关联文章树形结构
需求
- 按照最顶级节点数分页,不是所有数据条数
- 返回一个树形结构
- 根据权限进行数据结构筛选
优化方案
- 前情回顾
之前使用了先查出项目的全表,然后在代码中新建list存储后进行筛选,筛选完利用全表数据进行组树,这样如果只有几千条数据,整体速度是在1s以下,还是可以接受,但是如果数据量一旦到10w、100w 就会造成严重的卡顿。
当时考虑真分页的困难在于,在筛选的情况下,如何保证最后返回到前端的条数是一致的,因为在筛选后分页的数据还要进行组树,这样无法保证条数一致,后来考虑过补树,不过过于麻烦,就放弃了。
- 格局打开
既然单独的返回条数由于组树导致无法固定,换个角度,从前端来看,每次前端都是要首先显示一级项目,那么是不是可以先把符合筛选条件的一级项目查出来,然后再查询出每个树,然后再进行组树呢?
由于表结构是使用(id,pid)的方式来存储树形结构,所以要想实现树形结构真分页需要再增加一个字段firstNode(顶级节点),因为所谓分页就是将最后返回去的数据分批返回,所以就必须确定分页前的数据量是固定的
SELECT distinct firstNode FROM Project p where p.name='龙腾集团项目'
上述sql就可以查出所有要返回前端的所有树形项目的一级项目id,并且是不重复的,所以每次分页都能保证返回的条数是固定的。
接下来 对PageResult firstNodeList
进行再次查询
<if test="idList != null"> AND firstNode in <foreach close=")" collection="idList" index="" item="item" open="(" separator=","> #{item} </foreach> </if>
上述sql可以查出所有要返回前端的完整的树的项目,但是,其实是查多了,因为从一级项目查出所有的项目,就会将之前过滤的子项目又全部补全,实际上只筛选了一级项目,那么就要对项目进行再次筛选,因为我们项目后续有一些计算,所以把所有数据都查出来之后计算后使用的java筛选,没有必要就可以直接在上述sql直接筛选即可。
public List<Project> getSortProjectList(List<Project> projectList, ProjectVo query) { List<Project> projects = projectList.stream() .filter(e -> query.getId() == null || isContaint(e.getId(), query.getId())) .filter(e -> query.getProjectName() == null || isContaint(e.getProjectName(), query.getProjectName())) .filter(e -> query.getProjectStatus() == null || query.getProjectStatus().equals(e.getProjectStatus())) .filter(e -> query.getBusinessId() == null || query.getBusinessId().equals(e.getBusinessId())) .filter(e -> query.getProjectType() == null || isContaint(e.getProjectType(), query.getProjectType())) .filter(e -> query.getBusinessName() == null || isContaint(e.getBusinessName(), query.getBusinessName())) .filter(e -> query.getCustomer() == null || query.getCustomer().equals(e.getCustomer())) .filter(e -> query.getCreateUser() == null || query.getCreateUser().equals(e.getCreateUser())) .filter(e -> query.getProjectManager() == null || query.getProjectManager().equals(e.getProjectManager())) .filter(e -> query.getPriceId() == null || query.getPriceId().equals(e.getPriceId())) .filter(e -> query.getPriceFlag() == null || isContaint(query.getPriceFlag(), e.getPriceFlag())) .collect(Collectors.toList()); return projects; }
筛选结束后,进行组树
/** * 从一级向下递归 * * @param projectFatherList * @return */ public List<Project> getProjectTree(List<Project> projectFatherList, HashMap<String, List<Project>> allProjectMap) { //遍历,拿出所有的pidw为零的项目 List<Project> firstLeaveProject = projectFatherList.stream().filter(project -> StringUtils.isEmpty(project.getPId())).collect(Collectors.toList()); projectFatherList = getAllBottomProject(firstLeaveProject, allProjectMap); return projectFatherList; }
/** * 递归遍历此项目下所有项目 * * @param leaveProjectList * @return */ public List<Project> getAllBottomProject(List<Project> leaveProjectList, HashMap<String, List<Project>> allProjectMap) { if (leaveProjectList != null && leaveProjectList.size() != 0 && !allProjectMap.isEmpty()) { for (int i = leaveProjectList.size() - 1; i >= 0; i--) { if (leaveProjectList.get(i).getId() != null && allProjectMap.get(leaveProjectList.get(i).getId()) != null) { leaveProjectList.get(i).setChildrenProjectList(allProjectMap.get(leaveProjectList.get(i).getId())); //递归调用,查看子集是否还有子集 getAllBottomProject(leaveProjectList.get(i).getChildrenProjectList(), allProjectMap); } } } return leaveProjectList; }
/** * 向上递归 * * @param projectFatherList * @param projectList * @param projectAllList * @throws IOException * @throws ClassNotFoundException */ public void selectTreeUp(List<Project> projectFatherList, List<Project> projectList, List<Project> projectAllList) { if (!StringUtils.isEmpty(projectFatherList) && projectFatherList.size() > 0) { List<Project> projectTempList = new LinkedList<>(); for (Project k : projectFatherList ) { // 如果该项目有父项目,则将父项目放入返回list(projectAllList) if (!StringUtils.isEmpty(k.getPId())) { List<Project> collect = projectList.stream().filter(e -> e.getId().equals(k.getPId())).collect(Collectors.toList()); if (collect.size() > 0) { projectTempList.add(collect.get(0)); } } } projectAllList.addAll(projectTempList); projectFatherList = new LinkedList<>(); projectFatherList.addAll(projectTempList); selectTreeUp(projectFatherList, projectList, projectAllList); } } /** * 向下递归 * * @param projectSonList * @param projectList * @param projectAllList * @throws IOException * @throws ClassNotFoundException */ public void selectTreeBottom(List<Project> projectSonList, List<Project> projectList, List<Project> projectAllList) { if (!StringUtils.isEmpty(projectSonList) && projectSonList.size() > 0) { List<Project> projectTempList = new LinkedList<>(); List<Project> projectOrList; for (Project k : projectSonList ) { projectOrList = projectList.stream().filter(e -> k.getId().equals(e.getPId())).collect(Collectors.toList()); projectAllList.addAll(projectOrList); projectTempList.addAll(projectOrList); } projectSonList = new LinkedList<>(); projectSonList.addAll(projectTempList); selectTreeBottom(projectSonList, projectList, projectAllList); } }
然后各种需求操作,即可返回一个真分页的树形结构,速度杠杠的!
补充
这样有一个问题,会导致数据无法按照创建时间排序
SELECT DISTINCT projectFullPath FROM PSAProject p LEFT JOIN SalesProject a ON a.ID= p.BusinessId ORDER BY t1.CreateTime DESC [42000] [Microsoft][SQL Server Native Client 10.0][SQL Server]无法绑定由多个部分组成的标识符 "t1.CreateTime"。 (4104) [42000] [Microsoft][SQL Server Native Client 10.0][SQL Server]如果指定了 SELECT DISTINCT,那么 ORDER BY 子句中的项就必须出现在选择列表中
解决方案如下:
select p.projectFullPath ,p.CreateTime from PSAProject p where id in( SELECT DISTINCT projectFullPath FROM PSAProject p LEFT JOIN SalesProject a ON a.ID= p.BusinessId) order by p.CreateTime desc;