在Web開發中常常遇到樹形數據的操作,如菜單、組織機構、行政區(省、市、縣)等具有層級關系的數據。
以下以行政區為例說明樹形數據(層級關系數據)的存儲以及實現,效果如圖所看到的。
1 數據庫表結構設計
樹形數據一般通過父節點和子節點實現數據之間的層級關聯,層級關系在數據庫中主要通過主鍵和外鍵來實現。
--使用Oracle數據庫 --創建行政區表 create table TB_XZQ ( code NUMBER not null, --行政區編碼,主鍵 parent_code NUMBER, --上級行政區編碼,假設沒有上級行政區。則為空 name VARCHAR2(50) --行政區名稱 ); --設置CODE為主鍵 alter table TB_XZQ add constraint PK_TB_XZQ primary key (CODE) using index; --設置外鍵 alter table TB_XZQ add constraint FK_TB_XZQ_PARENT_CODE foreign key (PARENT_CODE) references TB_XZQ (CODE) on delete set null; --插入行政區數據 INSERT INTO TB_XZQ (CODE,PARENT_CODE,NAME) VALUES (420000, NULL, '湖北省'); INSERT INTO TB_XZQ (CODE,PARENT_CODE,NAME) VALUES (420100, 420000, '武漢市'); INSERT INTO TB_XZQ (CODE,PARENT_CODE,NAME) VALUES (420101, 420100, '市轄區'); INSERT INTO TB_XZQ (CODE,PARENT_CODE,NAME) VALUES (420102, 420100, '江岸區'); INSERT INTO TB_XZQ (CODE,PARENT_CODE,NAME) VALUES (420103, 420100, '江漢區'); INSERT INTO TB_XZQ (CODE,PARENT_CODE,NAME) VALUES (420104, 420100, '硚口區'); INSERT INTO TB_XZQ (CODE,PARENT_CODE,NAME) VALUES (420105, 420100, '漢陽區'); INSERT INTO TB_XZQ (CODE,PARENT_CODE,NAME) VALUES (421000, 420000, '荊州市'); INSERT INTO TB_XZQ (CODE,PARENT_CODE,NAME) VALUES (421001, 421000, '市轄區'); INSERT INTO TB_XZQ (CODE,PARENT_CODE,NAME) VALUES (421002, 421000, '沙市區'); INSERT INTO TB_XZQ (CODE,PARENT_CODE,NAME) VALUES (421003, 421000, '荊州區'); INSERT INTO TB_XZQ (CODE,PARENT_CODE,NAME) VALUES (430000, NULL, '湖南省'); INSERT INTO TB_XZQ (CODE,PARENT_CODE,NAME) VALUES (430100, 430000, '長沙市'); INSERT INTO TB_XZQ (CODE,PARENT_CODE,NAME) VALUES (430101, 430100, '市轄區'); INSERT INTO TB_XZQ (CODE,PARENT_CODE,NAME) VALUES (430102, 430100, '芙蓉區'); INSERT INTO TB_XZQ (CODE,PARENT_CODE,NAME) VALUES (430103, 430100, '天心區'); INSERT INTO TB_XZQ (CODE,PARENT_CODE,NAME) VALUES (430104, 430100, '岳麓區');
插入后數據例如以下圖所看到的
2 樹形數據Java實現
通常情況下,從數據庫中讀取的數據須要轉換為樹形結構。TreeNode是一個Java樹形數據實現類。通過靜態方法buildTree能夠方便的把TreeNode構建成樹。
import java.util.ArrayList; import java.util.List; /** * 樹節點。支持Ext、zTree等Web控件 * * @author accountwcx@qq.com * @param <T> 樹節點的綁定數據類 */ public class TreeNode<T> { /** * 樹節點id * 為了兼容多種情況,使用String類型 */ private String id; /** * 樹節點上級id */ private String parentId; /** * 樹節點顯示文本 */ private String text; /** * 樹節點名稱。內容和text一樣 * 該字段主要是為了兼容Ext和zTree */ private String name; /** * 是否為葉子節點 */ private Boolean leaf = true; private Boolean expanded = false; private T nodeData; /** * 是否為父節點,該字段和leaf反復,主要是為了兼容Ext和zTree */ private Boolean isParent = false; /** * 子節點。假設沒有子節點,則列表長度為0 */ private List<TreeNode<T>> children = new ArrayList<TreeNode<T>>(); public String getId() { return id; } public void setId(String id) { this.id = id; } public String getParentId() { return parentId; } public void setParentId(String parentId) { this.parentId = parentId; } public String getText() { return text; } public void setText(String text) { this.name = text; this.text = text; } public String getName() { return name; } public void setName(String name) { this.name = name; this.text = name; } public Boolean getExpanded() { return expanded; } public void setExpanded(Boolean expanded) { this.expanded = expanded; } public List<TreeNode<T>> getChildren() { return children; } public void setLeaf(Boolean leaf) { this.leaf = leaf; this.isParent = !leaf; } public Boolean getLeaf() { return this.leaf; } public Boolean getIsParent() { return isParent; } public void setIsParent(Boolean isParent) { this.isParent = isParent; this.leaf = !isParent; } public T getNodeData() { return nodeData; } public void setNodeData(T nodeData) { this.nodeData = nodeData; } /** * 把樹節點列表構造成樹,最后返回樹的根節點,假設傳入的列表有多個根節點,會動態創建一個根節點。* @param nodes 樹節點列表 * @return 根節點 */ public static <T> TreeNode<T> buildTree(List<TreeNode<T>> nodes){ if(nodes == null || nodes.size() == 0){ return null; } if(nodes.size() == 1){ return nodes.get(0); } //用來存放nodes里面的頂級樹節點 //也就是把沒有父節點的節點都放到tops里面去 List<TreeNode<T>> tops = new ArrayList<TreeNode<T>>(); boolean hasParent = false; //第一次遍歷,獲取一個節點作為子節點 for(TreeNode<T> child : nodes){ hasParent = false; //當前節點child的父節點id String pid = child.getParentId(); //假設pid不存在或為空 //則當前節點為頂級節點 if(pid == null || pid.equals("")){ //把當前節點加入到tops中作為頂級節點 tops.add(child); //跳過當前節點,進入下一輪 continue; } //遍歷nodes上的全部節點,推斷是否有child的父節點 for(TreeNode<T> parent : nodes){ String id = parent.getId(); //假設parent節點的id等於child節點的pid,則parent節點是child節點的父節點 if(id != null && id.equals(pid)){ //把child加到parent下 parent.getChildren().add(child); parent.setLeaf(false); //child節點有父節點 hasParent = true; continue; } } //假設child節點沒有父節點。則child是頂級節點 //把child加入到tops中 if(!hasParent){ tops.add(child); } } TreeNode<T> root; if(tops.size() == 1){ //假設頂級節點僅僅有一個。該頂級節點是根節點 root = tops.get(0); }else{ //假設頂級節點有多個,創建一個根節點,把頂級節點放到根節點下 root = new TreeNode<T>(); root.setLeaf(false); root.setId("-1"); root.setName("root"); root.setParentId(""); root.getChildren().addAll(tops); } return root; } }
3 生成行政區樹
Dao(僅僅列出主要代碼)
@Repository("xzqDao") public class XzqDaoImpl implements XzqDao { @Resource private JdbcTemplate jdbcTemplate; public List<XzqEntity> select() { String sql = "SELECT CODE,PARENT_CODE,NAME FROM TB_XZQ"; //用Spring JDBC進行數據庫操作 return jdbcTemplate.query(sql, new RowMapper<XzqEntity>(){ public XzqEntity mapRow(ResultSet rs, int index) throws SQLException { XzqEntity xzq = new XzqEntity(); xzq.setCode(rs.getInt("CODE")); xzq.setName(rs.getString("NAME")); xzq.setParentCode(rs.getInt("PARENT_CODE")); return xzq; } }); } }
Service(僅僅列主要代碼)
@Service("xzqService") public class XzqServiceImpl implements XzqService { @Resource private XzqDao xzqDao; public TreeNode<XzqEntity> tree(){ List<XzqEntity> list = xzqDao.select(); List<TreeNode<XzqEntity>> nodes = new ArrayList<TreeNode<XzqEntity>>(); //把行政區類轉為樹節點 for(XzqEntity xzq : list){ TreeNode<XzqEntity> node = new TreeNode<XzqEntity>(); //節點id node.setId(xzq.getCode().toString()); //節點上級id node.setParentId(xzq.getParentCode().toString()); node.setText(xzq.getName()); //把行政區類放到節點數據中,以備使用 node.setNodeData(xzq); nodes.add(node); } return TreeNode.buildTree(nodes); } }
Controller(僅僅列出主要代碼)
@Controller @RequestMapping("/xzq") public class XzqController { @Resource private XzqService xzqService; /** * 行政區樹,返回JSON格式 * * @param response */ @RequestMapping("/tree.mvc") public void tree(HttpServletResponse response) { String json = ""; try { json = JSON.toJSONString(xzqService.tree()); } catch (Exception e) { e.printStackTrace(); } //輸出JSON數據 //這里直接通過response輸出JSON字符串 //Spring MVC也提供了輸出JSON數據的方法 // 設置編碼格式 response.setContentType("text/plain;charset=utf-8"); response.setCharacterEncoding("utf-8"); PrintWriter out = null; try { out = response.getWriter(); out.write(json); out.flush(); } catch (IOException e) { e.printStackTrace(); } } }
ztree顯示行政區樹
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <% String context = request.getContextPath(); %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <link type="text/css" rel="stylesheet" href="./lib/ztree/zTreeStyle.css" /> <script type="text/javascript" src="./lib/jquery/jquery-1.11.2.min.js"></script> <script type="text/javascript" src="./lib/ztree/jquery.ztree.all-3.5.min.js"></script> <title>行政區</title> </head> <body> <!-- 行政區樹 --> <ul class="ztree" id="xzqtree" style="width:180px;height:350px;margin:10px;border:1px solid blue;overflow:auto;"></ul> <script type="text/javascript"> $(function(){ //獲取行政區數據 $.ajax({ url: './xzq/tree.mvc', dataType: 'json' }).done(function(data){ if(!data){ return; } //初始化行政區樹 $.fn.zTree.init($('#xzqtree'), {}, data); }); }); </script> </body> </html>