Web中树形数据(层级关系数据)的实现—以行政区树为例

时间:2023-03-09 06:41:09
Web中树形数据(层级关系数据)的实现—以行政区树为例

在Web开发中常常遇到树形数据的操作,如菜单、组织机构、行政区(省、市、县)等具有层级关系的数据。

以下以行政区为例说明树形数据(层级关系数据)的存储以及实现,效果如图所看到的。

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, '岳麓区');

插入后数据例如以下图所看到的

Web中树形数据(层级关系数据)的实现—以行政区树为例

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>