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, '岳麓区');
插入后数据例如以下图所看到的
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>
Web中树形数据(层级关系数据)的实现—以行政区树为例的更多相关文章
- 【吐血分享】SQL Server With As 递归获取层级关系数据
		
纯洁的一周又开始了,今天看到一则新闻,笑尿了,和袁友们一起娱乐下 最近两月在做基于Saas模式的人力资源管理产品,平常数据库设计我经常会遇到如下需求场景: 以前商城类网站在设计类型表的时候,设计成单表 ...
 - java web中各种context的关系
		
我举得这篇文章解决了我的很多疑惑,理清了我以前不太清楚的Context关系,读懂这篇文章很有助于理解源码, 原文链接在这里:https://www.jianshu.com/p/2537e2fec546 ...
 - SQL Server 利用WITH AS递归获取层级关系数据
		
WITH AS短语,也叫做子查询部分(subquery factoring),在SQL Server 2005中提供了一种解决方案,这就是公用表表达式(CTE),使用CTE,可以使SQL语句的可维护性 ...
 - Working with Data »  Getting started with ASP.NET Core and Entity Framework Core using Visual Studio »  更新关系数据
		
Updating related data¶ 7 of 7 people found this helpful The Contoso University sample web applicatio ...
 - sql中对查询出来的数据进行分页
		
当sql中存储的数据量比较大时,在web中 数据显示时都会对数据进行分页,分页不会在客户端进行分页,而是在数据库查询过程中进行了分页. sql代码: DECLARE @pageindex INT; - ...
 - Programming In Scala笔记-第十一章、Scala中的类继承关系
		
本章主要从整体层面了解Scala中的类层级关系. 一.Scala的类层级 在Java中Object类是所有类的最终父类,其他所有类都直接或间接的继承了Object类.在Scala中所有类的最终父类为A ...
 - 在GridControl表格控件中实现多层级主从表数据的展示
		
在一些应用场景中,我们需要实现多层级的数据表格显示,如常规的二级主从表数据展示,甚至也有多个层级展示的需求,那么我们如何通过DevExpress的GridControl控表格件实现这种业务需求呢?本篇 ...
 - 设计与开发一款简单易用的Web报表工具(支持常用关系数据及hadoop、hbase等)
		
EasyReport是一个简单易用的Web报表工具(支持Hadoop,HBase及各种关系型数据库),它的主要功能是把SQL语句查询出的行列结构转换成HTML表格(Table),并支持表格的跨行(Ro ...
 - WPF:使用Json.NET在TreeView中树形显示JSON数据
		
原文 WPF:使用Json.NET在TreeView中树形显示JSON数据 据 读者可以参考这个开源的可以树形显示XML和JSON的工具: Mgen Object 603:XML/JSON树形显示小工 ...
 
随机推荐
- cocos2d-android  使用 cocos2d 绘图
			
韩梦飞沙 韩亚飞 313134555@qq.com yue31313 han_meng_fei_sha cocos2d-android-1 https://github.com/ZhouWei ...
 - hdu 4790 Just Random
			
思路:对于a<=x<=b,c<=y<=d,满足条件的结果为ans=f(b,d)-f(b,c-1)-f(a-1,d)+f(a-1,c-1). 而函数f(a,b)是计算0<= ...
 - SQL Server 事务复制爬坑记
			
SQL Server 复制功能折腾了好几天了,现特将其配置过程以及其间遇到的问题记录下来,以备日后查阅.同时,也让“同道”同学们少走不必要的弯路.如果有不对之处,欢迎大家指正,欢迎沟通交流. 一.复制 ...
 - 中国剩余定理 hdu 1573 X问题
			
HDU 1573 X问题 Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Tota ...
 - SCOJ 4429: frog's dice 最大流
			
4429: frog's dice 题目连接: http://acm.scu.edu.cn/soj/problem.action?id=4429 Description frog has many d ...
 - PAT甲级1076. Forwards on Weibo
			
PAT甲级1076. Forwards on Weibo 题意: 微博被称为中文版的Twitter.微博上的一位用户可能会有很多关注者,也可能会跟随许多其他用户.因此,社会网络与追随者的关系形成.当用 ...
 - CentOS 6.9永久设置静态路由表以及路由表常用设置
			
一.路由表常用设置: 1.使用route命令添加的路由,机器重启或者网卡重启后路由就失效了,方法: //添加到主机的路由 # route add –host 192.168.1.11 dev eth0 ...
 - [转].net reactor 学习系列(四)---.net reactor应用场景
			
前面已经学习了.net reactor一些基础知识,现在准备学习下实际的应用场景,只是简单的保护和许可证发放场景.如果想应用更高级的场景比如自动化程序许可证的发放及自定义客户端的过期提示等等就需要自己 ...
 - Ubuntu下gcc多版本共存和版本切换
			
https://my.oschina.net/u/2306127/blog/538139 摘要: Ubuntu系统使用的gcc版本随着发布版本的不同而不同,在编译android系统时不同的版本推荐用不 ...
 - Memcached源码分析——连接状态变化分析(drive_machine)
			
这篇文章主要介绍Memcached中,基于libevent构造的主线程和worker线程所处理连接的状态互相转换的过程(不涉数据的存取等操作),也就是drive_machine的主要业务逻辑了.状态转 ...