树结构、树表、树形结构表的设计和用法

有些同仁对于 JeeSite 4 中的树表设计不太了解,本应简单的方法就可实现,却写了很多复杂的语句和代码,所以有了这篇文章。在 JeeSite 4 中的树表设计我还是相对满意的,这种设计比较容易理解,不会太依赖数据库的语法,对兼容多数据库比较好。相比网上大牛的左右值树设计简单了很多,并且可随时调换父节点,并级联更新所有子节点数据。

看下表字段说明我们发现除了父级节点外又多了很多辅助字段,这写字段的维护可能会稍微影响我们的插入和更新性能,但是这将极大的简化了我们的查询,并不限深度。废话不多说,下面我们一同来就来看看都有哪些好处。

树表字段说明

字段名说明
xxx_code节点编码(xxx表示用户自定义名称)
xxx_name节点名称(xxx表示用户自定义名称)
以下是树表关键字段:
parent_code节点上级编码
parent_codes节点所有上级编码(快速检索下级节点)
tree_sort当前层级排序号(decimal类型)
tree_sorts树节点的完整排序号,10位数字组成(快速整树排序)
tree_leaf是否是末级,是否叶子节点(0:否,1:是,char类型)
tree_level节点层次级别(从0开始,decimal类型,快速分级查询,根据层级缩进)
tree_names节点的全名称(用“/”分隔,快速获取当前节点完整路径)
以下是树表可选字段:
status节点状态(0:正常,1:删除,2:停用)
create_by创建者用户编码
create_date数据创建时间
update_by更新者用户编码
update_date数据更新时间

下面以区域树表举例

定义实体Entity

用户自定义的节点编码是area_code,节点名称是area_name,实体注解配置如下:

  1. @Table(name="${_prefix}sys_area", alias="a", columns={
  2. @Column(includeEntity=DataEntity.class),
  3. @Column(includeEntity=TreeEntity.class),
  4. @Column(name="area_code", attrName="areaCode", label="区域代码", isPK=true),
  5. @Column(name="area_name", attrName="areaName", label="区域名称", queryType=QueryType.LIKE, isTreeName=true),
  6. @Column(name="area_type", attrName="areaType", label="区域类型"),
  7. }, orderBy="a.tree_sorts, a.area_code"
  8. )
  9. public class Area extends TreeEntity<Area> {
  10. private String areaCode; // 区域代码
  11. private String areaName; // 区域名称
  12. private String areaType; // 区域类型(1:国家;2:省份、直辖市;3:地市;4:区县)
  13. // get set 省略
  14. }
  • Column 注解中的 includeEntity=TreeEntity.class 则自动导入树表关键字段配置(parent_code…)
  • Column 注解中的 includeEntity=DataEntity.class 则自动导入树表可选字段配置(create_by…)
  • 继承 TreeEntity 类就会自动拥有树表关键字段和可选字段的属性(parent_code…、create_by…)
  • 节点编码 Column 必须设置 isPk=true 来确定是一个唯一主键
  • 节点名称 Column 必须设置 isTreeName=true 来确定是一个节点名称,用来生成 tree_names 的值

定义业务层Service

下面我们介绍怎么来通过API操作这张表

  1. @Service
  2. @Transactional(readOnly=true)
  3. public class AreaService extends TreeService<AreaDao, Area> {
  4. }

该类继承了 TreeService,类的内容可以什么都不用写,就拥有了树表的增删改查等方法,以及树表维护的关键字段和可选字段的生成与更新。

下面我们就来展示下 TreeService 的方法:

  1. @Transactional(readOnly=true)
  2. public abstract class TreeService<D extends TreeDao<T>, T extends TreeEntity<T>> {
  3. /**
  4. * 获取单条数据
  5. * @param entity id
  6. */
  7. @Override
  8. public T get(T entity)
  9. /**
  10. * 根据父节点获取子节点最后一条记录
  11. * @param entity parentCode
  12. */
  13. public T getLastByParentCode(T entity)
  14. /**
  15. * 列表查询数据
  16. * @param entity
  17. */
  18. @Override
  19. public List<T> findList(T entity)
  20. /**
  21. * 查询列表总数
  22. * @param entity
  23. */
  24. @Override
  25. public long findCount(T entity)
  26. /**
  27. * 保存数据(插入或更新)
  28. * 实现自动保存字段:所有父级编号、所有排序号、是否是叶子节点、节点的层次级别等数据
  29. * 实现级联更新所有子节点数据:同父级自动保存字段
  30. */
  31. @Override
  32. @Transactional(readOnly=false)
  33. public void save(T entity)
  34. /**
  35. * 更新当前节点排序号
  36. */
  37. @Transactional(readOnly=false)
  38. public void updateTreeSort(T entity)
  39. /**
  40. * 预留接口事件,更新子节点
  41. * @param childEntity 当前操作节点的子节点
  42. * @param parentEntity 当前操作节点
  43. */
  44. protected void updateChildNode(T childEntity, T parentEntity)
  45. /**
  46. * 更新状态(级联更新父节点的tree_leaf字段)
  47. * @param entity
  48. */
  49. @Override
  50. @Transactional(readOnly=false)
  51. public void updateStatus(T entity)
  52. /**
  53. * 删除数据(级联删除子节点和父节点的tree_leaf字段)
  54. * @param entity
  55. */
  56. @Override
  57. @Transactional(readOnly=false)
  58. public void delete(T entity)
  59. /**
  60. * 修正本表树结构的所有父级编号
  61. * 包含:数据修复(parentCodes、treeLeaf、treeLevel)字段
  62. */
  63. @Transactional(readOnly=false) // 可读取未提交数据
  64. public void fixTreeData()
  65. /**
  66. * 按父级编码修正树结构的所有父级编号
  67. * 包含:数据修复(parentCodes、treeLeaf、treeLevel、treeSorts、treeNames)字段
  68. */
  69. @Transactional(readOnly=false) // 可读取未提交数据
  70. public void fixTreeData(String parentCode)
  71. /**
  72. * 修正指定节点及下级节点的所有父级编号(这是个递归程序)
  73. */
  74. private void fixTreeData(List<T> list, String parentCode, String parentCodes, String treeSorts, String treeNames)
  75. /**
  76. * 将不同级别无序的树列表进行排序,前提是sourcelist每一级是有序的<br>
  77. * 举例如下:<br>
  78. * List<T> targetList = ListUtils.newArrayList();<br>
  79. * List<T> sourceList = service.findList(entity);<br>
  80. * service.listTreeSort(targetList, sourceList, T.ROOT_CODE);<br>
  81. * @param sourceList 源数据列表
  82. * @param targetList 目标数据列表
  83. * @param parentCode 目标数据列表的顶级节点
  84. */
  85. public void listTreeSort(List<T> sourceList, List<T> targetList, String parentCode)
  86. /**
  87. * 将简单列表code,parentCode转换为嵌套列表形式[code,childList[code,childList[...]]]<br>
  88. * 举例如下:<br>
  89. * List<T> targetList = ListUtils.newArrayList();<br>
  90. * List<T> sourceList = service.findList(entity);<br>
  91. * service.convertChildList(targetList, sourceList, T.ROOT_CODE);<br>
  92. * @param sourceList 源数据列表
  93. * @param targetList 目标数据列表
  94. * @param parentCode 目标数据列表的顶级节点
  95. */
  96. public void convertChildList(List<T> sourceList, List<T> targetList, String parentCode)
  97. }

常用示例调用

保存一条数据:

  1. Area area = new Area();
  2. area.setIsNewRecord(true); // 代表新增还是更新
  3. area.setParentCode('370000'); // 上级编码
  4. area.setAreaCode('371000'); // 节点编码(唯一)
  5. area.setAreaName('济南市'); // 节点名称
  6. area.setTreeSort(1000); // 本级排序号
  7. areaService.save(area);

除了一些用户输入字段,其余辅助字段有save方法自动维护,调用者无需关心。

获取当前节点最大编号:

  1. Area where = new Area();
  2. where.setParentCode("370000");
  3. Area last = areaService.getLastByParentCode(where);
  4. System.out.println(last.getAreaCode());

查找下级子节点:

SQL:

  1. select a.* from sys_area a where a.area_code='370000'

API:

  1. Area where = new Area();
  2. where.setParentCode("370000");
  3. List<area> list = areaService.findList(where);
  4. System.out.println(list);

查找所有子节点:

SQL:

  1. select a.* from sys_area a where (a.area_code='370000' or a.parent_codes like '0,370000,%')

API:

  1. Area where = new Area();
  2. where.getSqlMap().getWhere().andBracket("area_code", QueryType.EQ, "370000", 1)
  3. .or("parent_codes", QueryType.LEFT_LIKE, "0,370000,%", 2).endBracket();
  4. List<area> list = areaService.findList(where);
  5. System.out.println(list);

只查找一级和二级节点:

SQL:

  1. select a.* from sys_area a where a.tree_level <= 1

API:

  1. Area where = new Area();
  2. where.getSqlMap().getWhere().and("tree_level", QueryType.LTE, 1);
  3. List<area> list = areaService.findList(where);
  4. System.out.println(list);

排除叶子节点:

SQL:

  1. select a.* from sys_area a where a.tree_leaf = '0'

API:

  1. Area where = new Area();
  2. where.setTreeLeaf("0");
  3. List<area> list = areaService.findList(where);
  4. System.out.println(list);

当前层级排序:

SQL:

  1. select a.* from sys_area a where a.parent_code = '0' order tree_sort asc

API:

  1. Area where = new Area();
  2. where.setParentCode("0");
  3. where.getSqlMap().getOrder().setOrderBy("a.tree_sort asc");
  4. List<area> list = areaService.findList(where);
  5. System.out.println(list);

全部层级排序:

SQL:

  1. select a.* from sys_area a where (a.area_code='370000' or a.parent_codes like '0,370000,%') order tree_sorts asc

API:

  1. Area where = new Area();
  2. where.getSqlMap().getWhere().andBracket("area_code", QueryType.EQ, "370000", 1)
  3. .or("parent_codes", QueryType.LEFT_LIKE, "0,370000,%", 2).endBracket();
  4. where.getSqlMap().getOrder().setOrderBy("a.tree_sorts asc");
  5. List<area> list = areaService.findList(where);
  6. System.out.println(list);

显示当前节点的全名称:

SQL:

  1. select a.tree_names from sys_area a where a.area_code='370000'

API:

  1. Area where = new Area();
  2. where.setAreaCode("370000");
  3. Area area = areaService.get(where);
  4. System.out.println(area.getTreeNames());

综合实例:

查询编号为370000,及所有下级的数据,并只查询2级数据,并按照升序排序

SQL:

  1. select a.* from sys_area a where (a.area_code='370000' or a.parent_codes like '0,370000,%') and a.tree_level <= 1 order by a.tree_sorts

API:

  1. Area where = new Area();
  2. where.getSqlMap().getWhere().andBracket("area_code", QueryType.EQ, "370000", 1)
  3. .or("parent_codes", QueryType.LEFT_LIKE, "0,370000,%", 2).endBracket();
  4. where.getSqlMap().getWhere().and("tree_level", QueryType.LTE, 1);
  5. where.getSqlMap().getOrder().setOrderBy("a.tree_sorts asc");
  6. List<area> list = areaService.findList(where);
  7. System.out.println(list);