JeeSite 4.x 樹形結構的表設計和用法

有些同仁對於 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 數據更新時間

下面以區域樹表舉例

定義實體Entity

用戶自定義的節點編碼是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 省略
}
  1. Column 註解中的 includeEntity=TreeEntity.class 則自動導入樹表關鍵字段配置(parent_code...)
  2. Column 註解中的 includeEntity=DataEntity.class 則自動導入樹表可選字段配置(create_by...)
  3. 繼承 TreeEntity 類就會自動擁有樹表關鍵字段和可選字段的屬性(parent_code...、create_by...)
  4. 節點編碼 Column 必須設置 isPk=true 來肯定是一個惟一主鍵
  5. 節點名稱 Column 必須設置 isTreeName=true 來肯定是一個節點名稱,用來生成 tree_names 的值

定義業務層Service

下面咱們介紹怎麼來經過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);
相關文章
相關標籤/搜索