有些同仁对于 JeeSite 4 中的树表设计不太了解,本应简单的方法就可实现,却写了不少复杂的语句和代码,因此有了这篇文章。数据库
在 JeeSite 4 中的树表设计我仍是相对满意的,这种设计比较容易理解,不会太依赖数据库的语法,对兼容多数据库比较好。相比网上大牛的左右值树设计简单了不少,而且可随时调换父节点,并级联更新全部子节点数据。ide
看下表字段说明咱们发现除了父级节点外又多了不少辅助字段,这写字段的维护可能会稍微影响咱们的插入和更新性能, 可是这将极大的简化了咱们的查询,并不限深度。废话很少说,下面咱们一同来就来看看都有哪些好处。性能
字段名 | 说明 |
---|---|
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 | 数据更新时间 |
用户自定义的节点编码是area_code,节点名称是area_name,实体注解配置以下:编码
@Table(name="${_prefix}sys_area", alias="a", columns={ @Column(includeEntity=DataEntity.class), @Column(includeEntity=TreeEntity.class), @Column(name="area_code", attrName="areaCode", label="区域代码", isPK=true), @Column(name="area_name", attrName="areaName", label="区域名称", queryType=QueryType.LIKE, isTreeName=true), @Column(name="area_type", attrName="areaType", label="区域类型"), }, orderBy="a.tree_sorts, a.area_code" ) public class Area extends TreeEntity<Area> { private String areaCode; // 区域代码 private String areaName; // 区域名称 private String areaType; // 区域类型(1:国家;2:省份、直辖市;3:地市;4:区县) // get set 省略 }
下面咱们介绍怎么来经过API操做这张表设计
@Service @Transactional(readOnly=true) public class AreaService extends TreeService<AreaDao, Area> { }
该类继承了 TreeService,类的内容能够什么都不用写,就拥有了树表的增删改查等方法,以及树表维护的关键字段和可选字段的生成与更新。code
下面咱们就来展现下 TreeService 的方法:排序
@Transactional(readOnly=true) public abstract class TreeService<D extends TreeDao<T>, T extends TreeEntity<T>> { /** * 获取单条数据 * @param entity id */ @Override public T get(T entity) /** * 根据父节点获取子节点最后一条记录 * @param entity parentCode */ public T getLastByParentCode(T entity) /** * 列表查询数据 * @param entity */ @Override public List<T> findList(T entity) /** * 查询列表总数 * @param entity */ @Override public long findCount(T entity) /** * 保存数据(插入或更新) * 实现自动保存字段:全部父级编号、全部排序号、是不是叶子节点、节点的层次级别等数据 * 实现级联更新全部子节点数据:同父级自动保存字段 */ @Override @Transactional(readOnly=false) public void save(T entity) /** * 更新当前节点排序号 */ @Transactional(readOnly=false) public void updateTreeSort(T entity) /** * 预留接口事件,更新子节点 * @param childEntity 当前操做节点的子节点 * @param parentEntity 当前操做节点 */ protected void updateChildNode(T childEntity, T parentEntity) /** * 更新状态(级联更新父节点的tree_leaf字段) * @param entity */ @Override @Transactional(readOnly=false) public void updateStatus(T entity) /** * 删除数据(级联删除子节点和父节点的tree_leaf字段) * @param entity */ @Override @Transactional(readOnly=false) public void delete(T entity) /** * 修正本表树结构的全部父级编号 * 包含:数据修复(parentCodes、treeLeaf、treeLevel)字段 */ @Transactional(readOnly=false) // 可读取未提交数据 public void fixTreeData() /** * 按父级编码修正树结构的全部父级编号 * 包含:数据修复(parentCodes、treeLeaf、treeLevel、treeSorts、treeNames)字段 */ @Transactional(readOnly=false) // 可读取未提交数据 public void fixTreeData(String parentCode) /** * 修正指定节点及下级节点的全部父级编号(这是个递归程序) */ private void fixTreeData(List<T> list, String parentCode, String parentCodes, String treeSorts, String treeNames) /** * 将不一样级别无序的树列表进行排序,前提是sourcelist每一级是有序的<br> * 举例以下:<br> * List<T> targetList = ListUtils.newArrayList();<br> * List<T> sourceList = service.findList(entity);<br> * service.listTreeSort(targetList, sourceList, T.ROOT_CODE);<br> * @param sourceList 源数据列表 * @param targetList 目标数据列表 * @param parentCode 目标数据列表的顶级节点 */ public void listTreeSort(List<T> sourceList, List<T> targetList, String parentCode) /** * 将简单列表code,parentCode转换为嵌套列表形式[code,childList[code,childList[...]]]<br> * 举例以下:<br> * List<T> targetList = ListUtils.newArrayList();<br> * List<T> sourceList = service.findList(entity);<br> * service.convertChildList(targetList, sourceList, T.ROOT_CODE);<br> * @param sourceList 源数据列表 * @param targetList 目标数据列表 * @param parentCode 目标数据列表的顶级节点 */ public void convertChildList(List<T> sourceList, List<T> targetList, String parentCode) }
Area area = new Area(); area.setIsNewRecord(true); // 表明新增仍是更新 area.setParentCode('370000'); // 上级编码 area.setAreaCode('371000'); // 节点编码(惟一) area.setAreaName('济南市'); // 节点名称 area.setTreeSort(1000); // 本级排序号 areaService.save(area);
除了一些用户输入字段,其他辅助字段有save方法自动维护,调用者无需关心。继承
Area where = new Area(); where.setParentCode("370000"); Area last = areaService.getLastByParentCode(where); System.out.println(last.getAreaCode());
SQL:递归
select a.* from sys_area a where a.area_code='370000'
API:接口
Area where = new Area(); where.setParentCode("370000"); List<area> list = areaService.findList(where); System.out.println(list);
SQL:
select a.* from sys_area a where (a.area_code='370000' or a.parent_codes like '0,370000,%')
API:
Area where = new Area(); where.getSqlMap().getWhere().andBracket("area_code", QueryType.EQ, "370000", 1) .or("parent_codes", QueryType.LEFT_LIKE, "0,370000,%", 2).endBracket(); List<area> list = areaService.findList(where); System.out.println(list);
SQL:
select a.* from sys_area a where a.tree_level <= 1
API:
Area where = new Area(); where.getSqlMap().getWhere().and("tree_level", QueryType.LTE, 1); List<area> list = areaService.findList(where); System.out.println(list);
SQL:
select a.* from sys_area a where a.tree_leaf = '0'
API:
Area where = new Area(); where.setTreeLeaf("0"); List<area> list = areaService.findList(where); System.out.println(list);
SQL:
select a.* from sys_area a where a.parent_code = '0' order tree_sort asc
API:
Area where = new Area(); where.setParentCode("0"); where.getSqlMap().getOrder().setOrderBy("a.tree_sort asc"); List<area> list = areaService.findList(where); System.out.println(list);
SQL:
select a.* from sys_area a where (a.area_code='370000' or a.parent_codes like '0,370000,%') order tree_sorts asc
API:
Area where = new Area(); where.getSqlMap().getWhere().andBracket("area_code", QueryType.EQ, "370000", 1) .or("parent_codes", QueryType.LEFT_LIKE, "0,370000,%", 2).endBracket(); where.getSqlMap().getOrder().setOrderBy("a.tree_sorts asc"); List<area> list = areaService.findList(where); System.out.println(list);
SQL:
select a.tree_names from sys_area a where a.area_code='370000'
API:
Area where = new Area(); where.setAreaCode("370000"); Area area = areaService.get(where); System.out.println(area.getTreeNames());
查询编号为370000,及全部下级的数据,并只查询2级数据,并按照升序排序
SQL:
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:
Area where = new Area(); where.getSqlMap().getWhere().andBracket("area_code", QueryType.EQ, "370000", 1) .or("parent_codes", QueryType.LEFT_LIKE, "0,370000,%", 2).endBracket(); where.getSqlMap().getWhere().and("tree_level", QueryType.LTE, 1); where.getSqlMap().getOrder().setOrderBy("a.tree_sorts asc"); List<area> list = areaService.findList(where); System.out.println(list);