实现web树
来源:互联网 发布:java javaagent 编辑:程序博客网 时间:2024/06/07 11:26
在我的博客数据库表的设计——自连接表的设计中,用一个商品分类表来说明怎样去设计一个无限极分类的表。如果我们要使用这样的一个无限极分类的表来实现web树,不可避免就要递归,如果树的层次很深,那么递归的次数就会很多,这时候极容易导致内存溢出。这样的表理论上可以保存无限极的分类,但在实际开发里面是不行的,因为树的层次过多,那么递归的次数就会过多,这样很容易导致内存溢出,又因为我们的计算机内存是有限的。
那么该如何设计数据库表来实现web树呢?
设计实现web树的数据库表
我们可以将商品分类设计成如下的树状结构:
再将其改造成如下样式:
树状节点的特点:
- 每一个节点都有一个左右值。
- 如果右值-左值=1,则代表当前节点为叶子节点。
- 如果右值-左值>1,则代表当前节点有孩子节点,值在左右值之间的所有节点,即为当前结点的所有孩子节点。
根据以上树状结构,我们可以使用如下SQL建表语句来设计数据库表:
create table category( id varchar(40) primary key, name varchar(100), lft int, rgt int);
并向category表中插入一些数据:
insert into category values('1','商品',1,18);insert into category values('2','平板电视',2,7);insert into category values('3','冰箱',8,11);insert into category values('4','笔记本',12,17);insert into category values('5','长虹',3,4);insert into category values('6','索尼',5,6);insert into category values('7','西门子',9,10);insert into category values('8','thinkpad',13,14);insert into category values('9','dell',15,16);
这时就会产生一个问题:为了在页面中显示树状结构,需要得到所有结点,以及每个结点在树中的层次。
解决思路:
- 要得到结点的层次,就是看节点有几个父亲,例如长虹有2个父亲,则它所在层次就为2。
如何知道每一个节点有几个父亲呢?这个表有个特点,父亲和孩子都在同一个表中,为得到父亲所有的孩子,可以把这张表想像成两张表,一张用于保存父亲,一张表保存孩子,如下所示:
select * from category parent,category child;
父亲下面的孩子有个特点,它的左值>父亲的左值,并且<父亲的右值,如下所示:
select * from category parent,category child where child.lft>=parent.lft and child.rgt<=parent.rgt;
以上语句会得到父亲下面所有的孩子。
对父亲所有孩子的姓名进行归组,然后使用count统计函数,这时就会知道合并了几个孩子,合并了几个孩子姓名,这个孩子就有几个父亲,从而知道它所在的层次。
select child.name,count(child.name) depth from category parent,category child where child.lft>=parent.lft and child.rgt<=parent.rgt group by child.name;
最后根据左值排序即可。
select child.name,count(child.name) depth from category parent,category child where child.lft>=parent.lft and child.rgt<=parent.rgt group by child.name order by child.lft;
我本人使用的是mysql 5.7.11,使用如下查询语句:
select child.id,child.name,child.lft,child.rgt,count(child.name) depth from category parent,category child where child.lft>=parent.lft and child.rgt<=parent.rgt group by child.name order by child.lft;
进行查询时,会报如下错误:
[Err] 1055 - Expression #1 of ORDER BY clause is not in GROUP BY clause and contains nonaggregated column ‘day17.child.lft’ which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
解决方法如下:
- 找到my.ini文件。
在my.ini文件里面设置
sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'
如下图:
- 重启MySQL服务,问题就可以解决了!
实现web树
现在我们来写代码实现web树。
创建MVC架构的Web项目
在Eclipse中新创建一个day17_tree的Web项目,导入项目所需要的开发包(jar包),创建项目所需要的包,在java开发中,架构的层次是以包的形式体现出来的。
项目所需要的开发包(jar包):
项目所需要的包:
以上就是根据此项目的实际情况创建的包,可能还需要创建其他的包,这个得根据项目的需要来定了。
由于我们在应用程序中加入了dbcp连接池,所以还应在类目录下加入dbcp的配置文件:dbcpconfig.properties。dbcpconfig.properties的配置信息如下:
#连接设置driverClassName=com.mysql.jdbc.Driverurl=jdbc:mysql://localhost:3306/day17username=rootpassword=yezi#<!-- 初始化连接 -->initialSize=10#最大连接数量maxActive=50#<!-- 最大空闲连接 -->maxIdle=20#<!-- 最小空闲连接 -->minIdle=5#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->maxWait=60000#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;] #注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。connectionProperties=useUnicode=true;characterEncoding=utf8#指定由连接池所创建的连接的自动提交(auto-commit)状态。defaultAutoCommit=true#driver default 指定由连接池所创建的连接的只读(read-only)状态。#如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)defaultReadOnly=#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLEdefaultTransactionIsolation=READ_COMMITTED
创建好的项目架构如下图所示:
分层架构的代码编写
分层架构的代码也是按照【域模型层(domain)】→【数据访问层(dao、dao.impl)】→【业务逻辑层(service、service.impl)】→【表现层(web.controller、web.UI、web.filter、web.listener)】→【工具类(util)】→【测试类(junit.test)】的顺序进行编写的。
开发domain层
在cn.itcast.domain包下创建一个Category类。
Category类的具体代码如下:
public class Category { private String id; private String name; private int lft; private int rgt; private int depth; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getLft() { return lft; } public void setLft(int lft) { this.lft = lft; } public int getRgt() { return rgt; } public void setRgt(int rgt) { this.rgt = rgt; } public int getDepth() { return depth; } public void setDepth(int depth) { this.depth = depth; }}
开发数据访问层(dao、dao.impl)
在开发数据访问层之前,先在cn.itcast.utils包下创建一个获取数据库连接的工具类(JdbcUtils)。
该工具类(JdbcUtils)的代码为:
public class JdbcUtils { private static DataSource ds = null; static { try { Properties prop = new Properties(); InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("dbcpconfig.properties"); prop.load(in); BasicDataSourceFactory factory = new BasicDataSourceFactory(); ds = factory.createDataSource(prop); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static DataSource getDataSource() { return ds; } public static Connection getConnection() throws SQLException { return ds.getConnection(); } /* * 工具类里面现在没有必要提供release()方法,因为我们是使用dbutils操作数据库, * 即调用dbutils的update()和query()方法操作数据库,他操作完数据库之后,会自动释放掉连接。 */}
提示:工具类里面现在没有必要提供release()方法,因为我们是使用DBUtils操作数据库,即调用DBUtils的update()和query()方法操作数据库,它操作完数据库之后,会自动释放掉连接。
在cn.itcast.dao包下创建一个CategoryDao类。
CategoryDao类的具体代码如下:
public class CategoryDao { public List<Category> getAll() { try { QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "select child.id,child.name,child.lft,child.rgt,count(child.name) depth from category parent,category child where child.lft>=parent.lft and child.rgt<=parent.rgt group by child.name order by child.lft"; List<Category> list = (List<Category>) runner.query(sql, new BeanListHandler(Category.class)); return list; } catch (Exception e) { throw new RuntimeException(e); } }}
照理说,开发完数据访问层,一定要对程序已编写好的部分代码进行测试。但我们有信心以上代码都不会有任何问题,这点自信都没有,搞鬼啊!
开发service层(service层对web层提供所有的业务服务)
在cn.itcast.service包下创建一个BusinessService类。
BusinessService类的具体代码如下:
public class BusinessService { public List<Category> getAllCategory() { CategoryDao dao = new CategoryDao(); return dao.getAll(); }}
同理,开发完业务逻辑层,一定要对程序已编写好的部分代码进行测试,但我们有信心业务逻辑层的代码没有任何问题,所以我们略过测试这一步。
开发Web层
实现web树
在cn.itcast.web.controller包中创建一个Servlet——ListTreeServlet,用于处理实现web树的请求。
ListTreeServlet类的具体代码如下:
public class ListTreeServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { BusinessService service = new BusinessService(); List<Category> list = service.getAllCategory(); request.setAttribute("list", list); request.getRequestDispatcher("/listtree.jsp").forward(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); }}
接下来就要创建显示web树的界面(listtree.jsp)了,该页面的创建有一点麻烦,需要我们从网上下载一个树控件:
解压该压缩包,将我们需要的xtree.js文件、xtree.css文件、images文件夹拷到我们的项目中,如下:
关于这个树控件怎么使用,可以参考其文档xtree/usage.html
。在这里就不详细讲解其使用方法了。我们直接给出listtree.jsp页面的代码:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><!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"><title>Insert title here</title><!-- The xtree script file --><script src="${pageContext.request.contextPath }/js/xtree.js"></script><!-- Modify this file to change the way the tree looks --><link type="text/css" rel="stylesheet" href="${pageContext.request.contextPath }/css/xtree.css"></head><body> <script type="text/javascript"> <c:forEach var="c" items="${list }"> <c:if test='${c.depth==1 }'> var tree = new WebFXTree('${c.name }'); </c:if> <c:if test='${c.depth==2 }'> var node${c.depth} = new WebFXTreeItem('${c.name }'); // node2 tree.add(node${c.depth}); </c:if> <c:if test='${c.depth>2 }'> var node${c.depth} = new WebFXTreeItem('${c.name }'); // node3 node${c.depth-1}.add(node${c.depth}); // node2.add(node3) </c:if> </c:forEach> document.write(tree); </script></body></html>
至此,实现web树的整个项目就圆满完成,现在我们来测试一把,测试结果如下:
可能出现的问题:当我们在浏览器中输入访问地址http://localhost:8080/day17_tree/ListTreeServlet
访问服务器,显示web树时,文件夹图片显示不出来,那一定是图片的路径写错了。xtree.js文件里面引用图片采用的是相对路径,那么相对路径相对的是谁呢?
答:相对的是http://localhost:8080/day17_tree
这个目录,该目录的下是有images目录的,所以我们显示web树是没有任何问题的。
给web树动态添加节点
例如,现在要给冰箱这个节点再添加一个海尔子节点,用图来表示即为:
上图就说明了动态地给web树添加节点的原理。现在我们写代码来实现动态地给web树添加节点的功能。
要实现该功能,需要修改JavaBean——Category.java的代码,Category类修改后的代码为:
public class Category { private String id; private String name; private int lft; private int rgt; private int depth; private List<Category> parents = new ArrayList<Category>(); // 记住该节点的所有父节点 public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getLft() { return lft; } public void setLft(int lft) { this.lft = lft; } public int getRgt() { return rgt; } public void setRgt(int rgt) { this.rgt = rgt; } public int getDepth() { return depth; } public void setDepth(int depth) { this.depth = depth; } public List<Category> getParents() { return parents; } public void setParents(List<Category> parents) { this.parents = parents; }}
修改数据访问层CategoryDao.java的代码,改后的代码如下:
public class CategoryDao { public List<Category> getAll() { try { QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "select child.id,child.name,child.lft,child.rgt,count(child.name) depth from category parent,category child where child.lft>=parent.lft and child.rgt<=parent.rgt group by child.name order by child.lft"; List<Category> list = (List<Category>) runner.query(sql, new BeanListHandler(Category.class)); return list; } catch (Exception e) { throw new RuntimeException(e); } } public Category getCategory(String id) { try { QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); // 查出节点的基本信息 String sql = "select * from category where id=?"; Category category = (Category) runner.query(sql, id, new BeanHandler(Category.class)); // 查出节点的父节点,填充list集合 sql = "select * from category where lft < ? and rgt > ? order by lft"; Object[] params = {category.getLft(), category.getRgt()}; List<Category> list = (List<Category>) runner.query(sql, params, new BeanListHandler(Category.class)); category.setParents(list); return category; } catch (Exception e) { throw new RuntimeException(e); } } public void addCategory(Category child) { try { QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "insert into category(id,name,lft,rgt) values(?,?,?,?)"; Object[] params = {child.getId(), child.getName(), child.getLft(), child.getRgt()}; runner.update(sql, params); } catch (Exception e) { throw new RuntimeException(e); } } public void insertUpdate(int rgt) { try { QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); String sql1 = "update category set lft=lft+2 where lft>=?"; String sql2 = "update category set rgt=rgt+2 where rgt>=?"; runner.update(sql1, rgt); runner.update(sql2, rgt); } catch (Exception e) { throw new RuntimeException(e); } }}
修改业务层BusinessService类的代码,改后的代码为:
public class BusinessService { private CategoryDao dao = new CategoryDao(); public List<Category> getAllCategory() { return dao.getAll(); } public Category getCategory(String id) { return dao.getCategory(id); } public void addCategory(String parent_id, String name) { Category parent = dao.getCategory(parent_id); // 得到要在哪个父节点下添加子节点 // 创建要添加的子节点 Category child = new Category(); child.setName(name); child.setId(UUID.randomUUID().toString()); child.setLft(parent.getRgt()); child.setRgt(child.getLft() + 1); dao.insertUpdate(parent.getRgt()); dao.addCategory(child); }}
在WebRoot根目录下新建网站首页页面——index.jsp。
index.jsp页面内容如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><!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"><title>网站首页(采用分帧技术)</title></head><frameset cols="15%,*"> <frame src="${pageContext.request.contextPath}/ListTreeServlet" name="left"> <frame src="" name="right"></frameset></html>
修改显示树的页面listtree.jsp,页面改后的内容如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><!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"><title>Insert title here</title><!-- The xtree script file --><script src="${pageContext.request.contextPath }/js/xtree.js"></script><!-- Modify this file to change the way the tree looks --><link type="text/css" rel="stylesheet" href="${pageContext.request.contextPath }/css/xtree.css"><script type="text/javascript"> <c:forEach var="c" items="${list }"> <c:if test='${c.depth==1 }'> var tree = new WebFXTree('${c.name }'); tree.action = "${pageContext.request.contextPath }/ViewCategoryServlet?id=${c.id }"; tree.target = "right"; </c:if> <c:if test='${c.depth==2 }'> var node${c.depth } = new WebFXTreeItem('${c.name }'); // node2 tree.add(node${c.depth }); node${c.depth}.action = "${pageContext.request.contextPath }/ViewCategoryServlet?id=${c.id }"; node${c.depth}.target = "right"; </c:if> <c:if test='${c.depth>2 }'> var node${c.depth } = new WebFXTreeItem('${c.name }'); // node3 node${c.depth-1 }.add(node${c.depth }); // node2.add(node3) node${c.depth}.action = "${pageContext.request.contextPath }/ViewCategoryServlet?id=${c.id }"; node${c.depth}.target = "right"; </c:if> </c:forEach> document.write(tree);</script></head><body></body></html>
在cn.itcast.web.controller包中创建一个Servlet——ViewCategoryServlet,用于处理显示web树节点详细信息的请求。
ViewCategoryServlet的具体代码如下:
public class ViewCategoryServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String id = request.getParameter("id"); BusinessService service = new BusinessService(); Category category = service.getCategory(id); request.setAttribute("c", category); request.getRequestDispatcher("/addcategory.jsp").forward(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); }}
在WebRoot根目录下新建一个添加分类的页面——addcategory.jsp。
addcategory.jsp页面的内容为:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><!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"><title>添加子类别</title></head><body> <br/> 您当前所在的位置: <c:forEach var="parent" items="${c.parents }"> ${parent.name } >>> </c:forEach> ${c.name } <br/><br/> 分类id:${c.id } 分类名称:${c.name } <form action="${pageContext.request.contextPath }/AddCategoryServlet" method="post"> <input type="hidden" name="pid" value="${c.id }"> <!-- 添加子节点的父节点id --> <input type="text" name="name"> <input type="submit" value="添加子类"> </form></body></html>
在cn.itcast.web.controller包中创建一个Servlet——AddCategoryServlet,用于处理添加web树节点的请求。
AddCategoryServlet的具体代码如下:
public class AddCategoryServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { request.setCharacterEncoding("UTF-8"); String parent_id = request.getParameter("pid"); String name = request.getParameter("name"); BusinessService service = new BusinessService(); service.addCategory(parent_id, name); request.setAttribute("message", "添加成功!!!"); } catch (Exception e) { e.printStackTrace(); request.setAttribute("message", "添加失败!!!"); } request.getRequestDispatcher("/message.jsp").forward(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); }}
最后在WebRoot根目录下新建一个全局消息显示页面——message.jsp。
message.jsp页面的内容为:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><!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"><title>Insert title here</title></head><body> ${message }</body></html>
至此,给web树动态地添加节点的功能,我们就已经实现了,小伙伴们,还不来快试试!!!
- 实现web树
- 实现web树
- java+ajax实现web目录树
- java+ajax实现web目录树
- C#实现WEB服务器
- java 实现web 登陆
- java 实现web 登陆
- C#实现WEB服务器
- java 实现web 登陆
- C#实现WEB服务器
- 实现 Web Service Security
- java 实现web 登陆
- C#实现WEB浏览器
- C#实现WEB服务器
- Web打印,简单实现
- Web打印,简单实现
- C#实现WEB服务器
- Web打印,简单实现
- codeforces369(2016.Aug.29)
- 剑指Offer_65_矩阵中的路径
- 面向对象的理解与设计
- 10013---PMP考试六大管理学定律
- 解决fbreader从书架打开的是同一本书问题
- 实现web树
- 屌丝学arm汇编-04-ldr的使用小结
- stm32 内部AD和DMA使用
- 冯大辉从丁香园离职,胜利者不应受到谴责吗?
- 关于隐藏属性与表单提交的问题
- python_使用asycnio协程的一些经验
- Java(字符流使用)
- Java使用第三方jar包实现邮件验证码
- 冯大辉这件事情上我支持大辉