应客户的要求,需要在定位平台上加多级部门的支持,可以无限分级,并将数据库中的部门及该部门下定位终端以目录树形式的显示在界面上,以数据库 目录树为关键字google了下,有参考价值的不多,大部门都是简单的显示下,格式也没有作缩进,很难看,客户肯定不满意,只能自己作了。要实现目录树肯定要用递归算法了。
要实现递归,必须得有一个父节点作为参考,如果原来的表里是没有这个字段,就给加上。看下部门表和终端表的结构
部门表(group_info)
字段名 类型 说明
-------------------------------------------------------------
group_id number(10) 部门id (PK)
group_name varchar2(20) 部门名称
enterprise_id number(10) 企业id
upper_group_id number(10) 上级部门id
终端表(locphone_list)
字段名 类型 说明
-------------------------------------------------------------
loc_phone varchar2(11) 手机号码 (PK)
loc_name varchar2(50) 持有者姓名
group_id number(10) 所属部门id
enterprise_id number(10) 所属企业id
给这两个表分别插入测试数据
部门表
insert into group_info (GROUP_ID, GROUP_NAME, ENTERPRISE_ID, UPPER_GROUP_ID)
values (42, '技术部', 1, -1);
insert into group_info (GROUP_ID, GROUP_NAME, ENTERPRISE_ID, UPPER_GROUP_ID)
values (1, '市场部', 1, -1);
insert into group_info (GROUP_ID, GROUP_NAME, ENTERPRISE_ID, UPPER_GROUP_ID)
values (6062, '软件开发部', 1, 42);
insert into group_info (GROUP_ID, GROUP_NAME, ENTERPRISE_ID, UPPER_GROUP_ID)
values (6063, '软件测评部', 1, 42);
insert into group_info (GROUP_ID, GROUP_NAME, ENTERPRISE_ID, UPPER_GROUP_ID)
values (6065, '定位系统开发', 1, 6062);
insert into group_info (GROUP_ID, GROUP_NAME, ENTERPRISE_ID, UPPER_GROUP_ID)
values (6066, '联通平台开发', 1, 6065);
insert into group_info (GROUP_ID, GROUP_NAME, ENTERPRISE_ID, UPPER_GROUP_ID)
values (5702, '综合部', 1, -1);
insert into group_info (GROUP_ID, GROUP_NAME, ENTERPRISE_ID, UPPER_GROUP_ID)
values (6064, '渔业软件', 1, 6062);
insert into group_info (GROUP_ID, GROUP_NAME, ENTERPRISE_ID, UPPER_GROUP_ID)
values (6067, '电信平台开发', 1, 6065);
终端表
insert into locphone_list (LOC_PHONE, LOC_NAME, GROUP_ID, ENTERPRISE_ID)
values ('18953415825', '小新', 42, 1);
insert into locphone_list (LOC_PHONE, LOC_NAME, GROUP_ID, ENTERPRISE_ID)
values ('18953645806', '小千', 6065, 1);
insert into locphone_list (LOC_PHONE, LOC_NAME, GROUP_ID, ENTERPRISE_ID)
values ('18953113253', '小波', 1, 1);
insert into locphone_list (LOC_PHONE, LOC_NAME, GROUP_ID, ENTERPRISE_ID)
values ('15318342218', '晓明', 6067, 1);
insert into locphone_list (LOC_PHONE, LOC_NAME, GROUP_ID, ENTERPRISE_ID)
values ('18953115549', '小朱', 5702, 1);
insert into locphone_list (LOC_PHONE, LOC_NAME, GROUP_ID, ENTERPRISE_ID)
values ('13335156298', '小李', 1, 1);
insert into locphone_list (LOC_PHONE, LOC_NAME, GROUP_ID, ENTERPRISE_ID)
values ('15318801656', '小徐', 42, 1);
insert into locphone_list (LOC_PHONE, LOC_NAME, GROUP_ID, ENTERPRISE_ID)
values ('15318803328', '小杨', 42, 1);
insert into locphone_list (LOC_PHONE, LOC_NAME, GROUP_ID, ENTERPRISE_ID)
values ('18953114426', '小玉', 6062, 1);
insert into locphone_list (LOC_PHONE, LOC_NAME, GROUP_ID, ENTERPRISE_ID)
values ('15318801259', '小程', 6062, 1);
insert into locphone_list (LOC_PHONE, LOC_NAME, GROUP_ID, ENTERPRISE_ID)
values ('15318805370', '张伟阳', 42, 1);
insert into locphone_list (LOC_PHONE, LOC_NAME, GROUP_ID, ENTERPRISE_ID)
values ('18953115808', '薛涛', 1, 1);
insert into locphone_list (LOC_PHONE, LOC_NAME, GROUP_ID, ENTERPRISE_ID)
values ('15318801313', '张培培', 1, 1);
insert into locphone_list (LOC_PHONE, LOC_NAME, GROUP_ID, ENTERPRISE_ID)
values ('13075306374', 'GPS_SMS', 42, 1);
用myeclipse新建一个项目取名为TreeShow,建一个数据库工具类,一个生成目录树的servlet,及一个用于显示的目录树的html,并在WebRoot目录下新建一个images文件夹将用到的图片拷贝到images文件夹
1、数据库工具类
package com.tdt.util;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public final class JdbcUtil {
private static String url = "jdbc:oracle:thin:@localhost:1521:orcl_db";
private static String user = "zwy";
private static String password = "zwy";
private JdbcUtil() {
}
static {
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
} catch (ClassNotFoundException e) {
throw new ExceptionInInitializerError(e);
}
}
//获取数据库连接对象
public static Connection getConnection() {
try {
return DriverManager.getConnection(url, user, password);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
/**
* 关闭数据库连接
* @param st
* @param rs
* @param conn
*/
public static void free(Statement st, ResultSet rs, Connection conn) {
try {
if (st != null) st.close();
if (rs != null) rs.close();
if (conn != null) conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
2、生成目录树的servlet
package com.tdt.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.tdt.util.JdbcUtil;
public class ShowTerminalTreeServlet extends HttpServlet {
private PrintWriter out;
private String sql="";
int divcode = 0;
int enterprise_id = 0;
String groupId="";
private static Connection conn = JdbcUtil.getConnection();
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
enterprise_id = 1;//Integer.parseInt((String)request.getSession().getAttribute("enterpriseid")); 这个地方应从登陆者的session获取企业id,为了方便查看效果直接赋值
response.setContentType("text/html;charset=utf-8");
out = response.getWriter();
try {
out.println("<img src='images/nolines_plus.gif' οnclick='changeState(this,0)'><img src='images/base.png' />全部终端"
+ "<div id=level0 style='display:none' >");
generateTree(-1, 0);//从根部门向下查找,根部门的group_id为 -1
out.println("</div>");
out.flush();
out.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
// 产生树结构
public void generateTree(int parentid, int level) throws SQLException {
level++;
divcode++;
// Statement和ResultSet不要声明为全局变量,如果声明全局变量有可能会出现结果集耗尽
Statement st = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_READ_ONLY);
sql ="select * from zwy.group_info where upper_group_id="+ parentid + " and enterprise_id =" + enterprise_id;
ResultSet rs = st.executeQuery(sql);
while (rs.next()) {
for (int i = 0; i < level - 1; i++) { // 控制缩进
out.print("<img src='images/line.gif' />");
}
int groupId =rs.getInt("group_id");
String groupName = rs.getString("group_name");
if ((existedChild(groupId) || existedTerminal(groupId))
&& rs.isLast()) {// 有下级部门或着在该部门中有终端,并且是最后一行
out.print("<img src='images/plusbottom.gif' οnclick='changeState(this,"+ divcode+ ")' />"+ groupName+ "<br />");
} else if ((existedChild(groupId) || existedTerminal(groupId))
&& !rs.isLast()) {
out .print("<img src='images/plus.gif' οnclick='changeState(this,"+ divcode+ ")' />"+ groupName+ "<br />");
} else if (!existedChild(groupId)&& !existedTerminal(groupId) && rs.isLast()) {// 没有下级部门也没有终端,且是最后一行
out.print("<img src='images/joinbottom.gif' οnclick='changeState(this,"+ divcode+ ")' />"+ groupName+ "<br />");
} else {
out.print("<img src='images/join.gif' οnclick='changeState(this,"+ divcode+ ")' />"+ groupName+ "<br />");
}
// 获得该部门下的所有终端
List<String[]> terminalList = getTerminalByGroup(group_id);
out.println("<div id='level" + divcode+ "' style='display:none;'>\n");
for (int x = 0; x < terminalList.size(); x++) {
if (!rs.isLast()) {
for (int i = 0; i < level; i++) {
out.print("<img src='images/line.gif' />");
}
} else if (x == (terminalList.size() - 1)) {// 最后一个终端
for (int i = 0; i < level - 1; i++) {
out.print("<img src='images/line.gif' />");
}
out.print("<img src='images/empty.gif' />");
} else {
for (int i = 0; i < level; i++) {
out.print("<img src='images/empty.gif' />");
}
}//上面的代码用于控制格式缩进
if (x != (terminalList.size() - 1)) {
out.println("<img src='images/join.gif' />"+terminalList.get(x)[1] +"<br />");
} else {// 最后一个终端
//判断是否还有下级
if(existedChild(groupId)) {
out.println("<img src='images/join.gif' />"+terminalList.get(x)[1] +"<br />");
} else {
out.println("<img src='images/joinbottom.gif' />"+terminalList.get(x)[1] +"<br />");
}
}
}
generateTree(groupId, level); // 递归调用,虽然又调用了函数generateTree,但是在概念上,我们应该把
// 它当作另外一个函数,每次当我们调用一个函数的时候--无论是递归调用
// 还是非递归调用,该函数都会得到自己独有的一组局部变量和形式变量
out.println("</div>");
}
JdbcUtil.free(st, rs, null);
}
// 检查是否有下级部门
public boolean existedChild(int groupid) throws SQLException {
Statement st = null;
ResultSet rs = null;
st = conn.createStatement();
sql = "select * from zwy.group_info where upper_group_id=" + groupid
+ " and enterprise_id=" + enterprise_id;
rs = st.executeQuery(sql);
boolean flag = rs.next();
JdbcUtil.free(st, rs, null);
return flag;
}
// 检查该部门中是否有终端
public boolean existedTerminal(int groupid) throws SQLException {
Statement st = null;
ResultSet rs = null;
st = conn.createStatement();
sql = "select * from zwy.locphone_list where group_id=" + groupid
+ " and enterprise_id=" + enterprise_id;
rs = st.executeQuery(sql);
boolean flag = rs.next();
JdbcUtil.free(st, rs, null);
return flag;
}
// 获取该部门所有的终端
public List<String[]> getTerminalByGroup(int groupid) throws SQLException {
Statement st = null;
ResultSet rs = null;
List<String[]> terminalList = null;
st = conn.createStatement();
sql = "select * from zwy.locphone_list where group_id=" + groupid
+ " and enterprise_id=" + enterprise_id;
rs = st.executeQuery(sql);
terminalList = new ArrayList<String[]>();
while (rs.next()) {
String[] args = new String[2];
args[0] = rs.getString("LOC_PHONE");
args[1] = rs.getString("LOC_NAME");
terminalList.add(args);
}
JdbcUtil.free(st, rs, null);
return terminalList;
}
public void destroy() {
//当servlet被销毁时,才关闭数据库的连接
JdbcUtil.free(null, null, conn);
}
}
3、显示目录树的index.html
<!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>
<style type="text/css">
body {
font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;
color: #666;
font-size: 13px;
white-space: nowrap;
}
img {
border: 0px;
vertical-align: middle;
}
#idshowTree input { //如果要在终端或着部门前面加上checkbox以便得到相应的值的话,最好能加上这样的格式。如果checkbox的size比line.gif图片的size大的话,左边控制缩进的坚线会有空白
width: 13px;
height: 13px;
margin-right: 3px;
}
</style>
<script type="text/javascript">
var xmlhttpReq;
//用于创建XMLHttpRequest对象
function createXmlHttp() {
//根据window.XMLHttpRequest对象是否存在使用不同的创建方式
if (window.XMLHttpRequest) {
xmlhttpReq = new XMLHttpRequest(); //FireFox、Opera等浏览器支持的创建方式
} else {
xmlhttpReq = new ActiveXObject("Microsoft.XMLHTTP");//IE浏览器支持的创建方式
}
}
//动态显示+、-号图片
function changeState(oDiv,iDivcode){
var object = document.getElementById("level"+iDivcode);
if(object.style.display=='none'){
object.style.display='block';
}else{
object.style.display='none';
}
if(oDiv.src.indexOf("images/minus.gif")>0) {
oDiv.src="images/plus.gif";
}else if (oDiv.src.indexOf("images/plus.gif")>0) {
oDiv.src="images/minus.gif";
}else if(oDiv.src.indexOf("images/minusbottom.gif")>0) {
oDiv.src="images/plusbottom.gif";
}else if(oDiv.src.indexOf("images/plusbottom.gif")>0) {
oDiv.src="images/minusbottom.gif";
}else if(oDiv.src.indexOf("images/nolines_plus.gif")>0) {
oDiv.src="images/nolines_minus.gif";
}else if(oDiv.src.indexOf("images/nolines_minus.gif")>0){
oDiv.src="images/nolines_plus.gif";
}
}
function initpage() {
createXmlHttp();
xmlhttpReq.open("GET","ShowTerminalTreeServlet?"+new Date().getTime(),true);
xmlhttpReq.send(null);
xmlhttpReq.onreadystatechange=function(){
if(xmlhttpReq.status == 200) {
if(xmlhttpReq.readyState == 4) {
var info =xmlhttpReq.responseText;
$("idshowTree").innerHTML=info;
}
}
}
}
function $(id) {
return document.getElementById(id);
}
</script>
</head>
<body οnlοad="initpage()">
<div id="idshowTree" >
</div>
</body>
</html>
4.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <servlet> <servlet-name>ShowTerminalTreeServlet</servlet-name> <servlet-class>com.tdt.servlet.ShowTerminalTreeServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>ShowTerminalTreeServlet</servlet-name> <url-pattern>/ShowTerminalTreeServlet</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> </web-app>
访问http://localhost:8080/TreeShow就能看到效果了
需要注意的问题:
1、不要将Statement和ResultSet声明为全局变量,如果声明为全局变量你会发现前几次会正常显示,多次刷新index.html会出现结果集耗尽的情况,导致index.html页面不会显示任何的内容
2、不要将获取数据库连接的语句放在generateTree()方法内,如果放到generateTree()方法内,那么每次递归调用generateTree()就连一次数据库,连接数据库是非常耗时的工作,根据TCP/IP 协议需要进行三次握手后才能建立连接,如果数据库服务器不在同一个局域网的话连接的时间会更长。在csdn看到有的朋友问为什么我递归显示数据库数据非常慢,就是这个原因造成的。
3、最好将Connection声明为static成员变量并赋值,使用完后不要关闭连接。这样另外一个人访问时就不需要再建立与数据库的连接,提高速度。要作的更好的话,将赋值语句放在servlet的init()方法内,然后再在web.xml指定load-on-startup,web容器一启动就完成数据库的连接。如果是使用数据库连接池就另说了。
我的测试环境为tomcat6.0+oracle 10g+J2EE1.4
图片我打包上传,代码都已经贴出来了。有需要的朋友留下邮箱,我会把完整的项目发到邮箱里。