通过数据库数据构建树
由 NetBeans 教程组创建
本教程将介绍如何动态地通过数据库中的数据构建树。我们将使用 NetBeans IDE 6.0 构建一个由两个页面组成的应用程序,它的第个页面将包含一个 Tree 组件。我们将在 Tree 组件的第一级节点中填充数据库中的人物姓名,并在第二级节点中填充该人物的旅程信息。旅程节点将链接到第二个页面,其中显示了该旅程的详细停息。
|
目录
本教程将涉及以下技术和资源:
JavaServer Faces 组件 Java EE 平台 |
1.2 与 Java EE 5* 1.1 与 J2EE 1.4
|
| Travel 数据库 |
必需 |
* 要利用 NetBeans IDE 6.0 的 Java EE 5 功能,我们需要使用一个与 Java EE 5 规范完全兼容的应用服务器,比如说 Sun Java System Application Server 9(GlassFish 项目)。
本教程适用于 GlassFish V2 应用服务器。如果使用的是不同的服务器,请参阅 发行说明 和 常见问题解答 了解各类问题和解决途径。有关所支持的服务器和 Java EE 平台的详细信息,请参阅 发行说明。
设计主页
首先,我们将构建一个主页,其中包含 Tree 组件和 TRIP 数据库表。下图显示了该页面。
 图 1:页面设计 |
-
创建一个新的 Visual Web JSF 应用程序项目,将它命名为
DatabaseTree,然后启用 Visual Web JavaServer Faces 框架。
-
从 Palette 的 Basic 部分拖动一个 Tree 组件到页面上,输入 Travel Information 并按下回车键。在 Properties 窗口中,将 id 属性设置为 CopyCustomerInfo,并将 clientSide 属性设置为 True。
当 clientSide 属性为 True 时,每个子节点(展开或未展开)都将发送给客户机,但是它们只有在展开父节点的时候才可见。当 clientSide 属性为 False 时,只有展开父节点的子节点才会被呈现。
-
选择 Tree Node 1 节点,单击鼠标右键并从弹出菜单中选择 Delete 选项。
对于本文中的应用程序,我们将通过编程填充树中的节点,因此不需要初始化由 IDE 创建的树节点。如果未移除节点,则 JSP 标记属性中设置的值将优先于运行时设置,并且页面将显示节点。
-
从 Palette 拖动一个 Message Group 组件到页面某角落中,比如说页面的右上角。
连接数据库:
接下来,我们将连接页面与 Travel 数据源中的一个数据库表。然后,我们将使用 Query Editor 修改用于检索数据的 SQL 查询语句,使旅行者姓名按字母排序并让旅行日期按时间排序。
-
打开 Projects 窗口,展开 Databases 节点,然后验证是否已连接到 Travel 数据库。
如果 TRAVEL 数据库 jdbc 节点的标志是裂开的并且无法展开该节点,则 IDE 未连接到数据库。要连接到 TRAVEL 数据库,右键单击 TRAVEL 数据库的 jdbc 节点并从弹出菜单中选择 Connect 选项。如果出现 Connect 对话框,则输入 travel 作为口令,选中 Remember Password During This Session 选项并单击 OK 按钮。
注意: 如果使用的是 Apache Tomcat 应用服务器,则应在尝试连接数据库之前将 derbyClient.jar 文件复制到 <tomcat_install>/common/lib 目录。
-
展开 TRAVEL 数据库的 jdbc 节点,然后展开 Tables 节点,如下图所示。
 图 2:Travel 数据库 |
将 TRIP 节点拖动到 Visual Designer 中。
Navigator 窗口将在 Page1 部分中显示一个 tripDataProvider 节点,并在 SessionBean1 部分中显示一个 tripRowSet 节点。
-
在 Navigator 窗口中,展开 SessionBean1 节点,然后右键单击 tripRowSet 节点并选择 Edit SQL Statement 选项。
Query Editor 将出现在编辑匹域,其中含有一个 TRIP 表图。
-
从 Services 窗口中拖出一个 Travel > Tables > PERSON 节点,并将它放置在 Query Editor 中的 Trip 表图旁边,如图 3 所示。
已有的两个表图中间将出现另一个表图,其中含有一个链接,也可以称作连接。
- 在 PERSON 表中,清除 PERSONID 复选框。
-
在 Query Editor 的 Design Grid 中,找到 TRAVEL.PERSON 表的 NAME 行。单击 Sort Type 表元并从下拉列表中选择 Ascending 选项。
此操作将使数据库表中的姓名根据姓的字母顺序排序。
-
找到 TRAVEL.TRIP 表的 DEPDATE 行。单击 Sort Type 表元并从下拉列表中选择 Ascending 选项。
此操作将使旅行日期根据时间先后排序。下图显示了 Query Editor。
 图:Query Editor |
通过数据库表构建树
现在,我们已经在存储停息中添加了一个请求 bean 属性,可供应用程序的中两个页面使用。然后,我们将在 prerender() 方法中添加代码,用于通过 TRIP 和 PERSON 数据库表动态地构建 Tree 组件。
-
打开页面 1 使 Navigator 窗口可见。在 Navigator 窗口中,右键单击 RequestBean1 节点并选择 Edit Java Source 选项。
-
在 public class RequestBean1 extends AbstractRequestBean 构造函数中声明属性,如下所示:
private Integer personId;
- 在 Java Editor 中单击鼠标右键并选择 Refactor > Encapsulate 字段。
- 在 Encapsulate Fields 对话框中,选中创建 getter 方法和 setter 方法选项,如下图所示。确保变量声明中字段可见性为私有类型,存取程序可见性为公有类型,然后单击 Refactor 按钮。
图 3:文本文件上传 |
-
在 Java Editor 中打开页面 1 并找到 prerender 方法。将 prerender 方法的主体部分替换为以下代码中的 黑体 部分:
| 代码示例 1:页面 1 的 prerender 方法 |
public void prerender() {
// If the Request Bean's personId is set, then
// we just came back from the Trip page
// and had displayed a selected trip.
// We use the personId later to determine whether
// to expand a person's node
Integer expandedPersonId = getRequestBean1().getPersonId();
try {
// Set up the variables we will need
Integer currentPersonId = new Integer(-1);
// If nbrChildren is not 0 then this is a
// postback and we have our tree already
int nbrChildren = displayTree.getChildCount();
if (nbrChildren == 0) {
// List of outer (person) nodes
List outerChildren = displayTree.getChildren();
// Erase previous contents
outerChildren.clear();
// List of inner (trip) nodes
List innerChildren = null;
// Execute the SQL query
tripDataProvider.refresh();
// Iterate over the rows of the result set.
// Every time we encounter a new person, add first level node.
// Add second level trip nodes to the parent person node.
boolean hasNext = tripDataProvider.cursorFirst();
while (hasNext) {
Integer newPersonId =
(Integer) tripDataProvider.getValue(
"TRIP.PERSONID");
if (!newPersonId.equals(currentPersonId)) {
currentPersonId = newPersonId;
TreeNode personNode = new TreeNode();
personNode.setId("person" + newPersonId.toString());
personNode.setText(
(String)tripDataProvider.getValue(
"PERSON.NAME"));
// If the request bean passed a person id,
// expand that person's node
personNode.setExpanded(newPersonId.equals
(expandedPersonId));
outerChildren.add(personNode);
innerChildren = personNode.getChildren();
}
// Create a new trip node
TreeNode tripNode = new TreeNode();
tripNode.setId("trip" +
tripDataProvider.getValue("TRIP.TRIPID").toString());
tripNode.setText(
tripDataProvider.getValue("TRIP.DEPDATE").toString());
tripNode.setUrl("/faces/Trip.jsp?tripId=" +
tripDataProvider.getValue("TRIP.TRIPID").toString());
innerChildren.add(tripNode);
hasNext = tripDataProvider.cursorNext();
}
}
} catch (Exception ex) {
log("Exception gathering tree data", ex);
error("Exception gathering tree data: " + ex);
}
}
|
以上代码将读取行程记录,该记录将根据 personId 排序。对于每个 personId,代码都将在 Tree 中创建一个新的一级节点。然后,代码将为与该 personId 关联的每一条行程记录创建一个二级节点(内嵌节点)。最后,代码会将二级节点绑到 tripNode_action 方法,我们将在本节稍后部分创建该方法。
- 在源代码中单击鼠标右键,并从弹出菜单中选择 Fix Imports 选项修复类未找到错误。在 Fix All Imports 对话框中,确保
java.util.List 出现在 List 字段中并单击 OK 按钮。
运行项目。
Web 浏览器将打开并显示一个 Tree 组件,其中每个一级节点显示的都是人物姓名,如下图所示。展开节点可显示该人物的旅行日期。注意到,姓名将根据姓的字母排序,而日期将根据时间排序,如下图所示。在下一节中,我们将添加一些代码,当用户单击行程节点时便会跳转到第二个页面。第二个页面将显示用户所选行程的详细停息。
图 5:动态树节点 |
添加详细信息页面
现在,我们将在应用程序中添加第二个页面,如下图所示。该页面将使用 Property Sheet 组件动态地显示用户在第一个页面中选择的行程的详细信息。
图 6:详细信息页面 |
-
打开 Projects 窗口,右键单击 Web Pages 节点并从弹出菜单中选择 New > Visual Web JSF Page 选项。将新页面命名为
Trip。
-
打开 Services 窗口,将 Tables > TRIP 节点拖动到 Trip 页面中的 Visual Designer 中。
Navigator 窗口将在 Trip 区域显示一个 tripDataProvider 节点,并在 SessionBean1 区域显示一个 tripRowSet 节点。
- 在 Navigator 窗口中,右键单击 tripRowSet1 节点并选择 Edit SQL Statement 选项。
在 Query Editor 的 Design Grid 中,右键单击 TRIPID 行中的任何表元并选择 Add Query Criteria 选项。在对话框中,将 Comparison 下拉列表设置为 =Equals 并选中 Parameter 单选按钮。单击 OK 按钮。
我们可以在 TRIPID 表的 Criteria 列中看到 =?,它会在 SQL 查询中添加以下 WHERE 语句。WHERE TRAVEL.TRIP.TRIPID = ?
- 在 Visual Designer 中打开 Trip 页面。从 Palette 的 Basic 部分拖动一个 Hyperlink 组件到页面上,输入
Home 并按下回车键。
在 Hyperlink 组件的 Properties 窗口中,单击 action 属性的 ellipsis 按钮,从下拉列表中选择 hyperlink1_action 选项,然后单击 OK 按钮。
IDE 会将 hyperlink1_action 事件处理程序添加到 Java 源代码中。
- 从 Palette 拖动一个 Message Group 组件到页面中,并将它放置在 Hyperlink 组件的右侧。
-
从 Palette 的 Layout 部分拖动一个 Property Sheet 组件到页面上。将它放置在 Hyperlink 组件的下面。
Property Sheet 组件将提供一个容器用于完成行程信息的布局。Property Sheet 组件含有一个 Property Sheet Section,而其中又含有一个 Property 组件。
选择 Property Sheet Section 1。在 Properties 窗口中,将 label 属性设置为 Trip Details。
注意:如果项目源代码等级设置为 1.4,则在 Properties 窗口中完成修改后不会更新属性表单。
-
在 Outline 窗口中,展开 propertySheet1 > section1 并选择 property1 节点。在 Properties 窗口中,将
label 属性设置为 Departure Date: 并按下回车键。
-
在 Outline 窗口中,选择 section1 节点并单击鼠标右键,然后从弹出菜单中选择 Add Property 选项。在 Properties 窗口中,将
label 属性设置为 Departure City: 并按下回车键。
从 Palette 中拖动一个 Static Text 组件到 Outline 窗口中的 property1 节点上。
Static Text 将成为 property1 节点的子节点。Static Text 还将出现在 Visual Designer 中。
右键单击 Static Text 组件并从弹出菜单中选择 Bind to Data 选项。如果需要,单击 Bind to Data Provider 选项卡使它显示在最前面。在对话框中,选择 Data 字段中的 TRIP.DEPDATE 选项,如下图所示,然后单击 OK 按钮。
当前日期将出现在 Visual Designer 的 Static Text 组件中。
图 8:Bind to Data 对话框 |
- 添加一个 Static Text 组件到 property2 中。将 Static Text 绑定到 TRIP.DEPCITY。
添加代码
现在,我们将添加一些代码,使 Trip 页面可以获取存储在页面 1 中的 tripid,并使页面 1 可以获取存储在 Trip 页面中的 personid。
-
在 Java Editor 中打开 Trip 页面并找到 prerender 方法。添加以下代码(显示为 黑体),使方法能够获取存储于页面 1 中的 tripId。
| 代码示例 2:Trip 页面的 prerender 方法 |
public void prerender() {
// Get the person id from the request parameters
String parmTripId = (String)
getExternalContext().getRequestParameterMap().get("tripId");
if (parmTripId != null) {
Integer tripId = new Integer(parmTripId);
try {
getSessionBean1().getTripRowSet1().setObject(1, tripId);
tripDataProvider.refresh();
} catch (Exception e) {
error("Cannot display trip " + tripId);
log("Cannot display trip " + tripId, e);
}
}else {
error("No trip id specified.");
}
}
|
setObject 方法将行程查询的第一个参数设置为 tripId。也就是说,方法会将查询中的 ? 替换为 tripId。这个查询只有一个参数,因此我们只需要调用setObject 一次。tripDataProvider1.refresh() 方法将调用 CachedRowSet.release() 并重置 CachedRowSetDataProvider 的光标。此时不会执行 CachesRowSet。
找到 hyperlink1_action 方法。添加以下代码(显示为 黑体),将 personId 传递给页面 1。
| 代码示例 3:Trip 页面的 hyperlink1_action 方法 |
public String hyperlink1_action() {
getRequestBean1().setPersonId(
(Integer)tripDataProvider.getValue("trip.personid"));
return null;
}
|
定义页面导航
最后,我们将指定从页面 1 中的节点到 Trip 页面的导航。
-
在 Visual Designer 的 Design 视图中任意一处单键鼠标右键,然后选择 Page Navigation 选项。
-
单击 Page1.jsp 图标上的连接器接口,然后拖动一个连接器到 Trip.jsp 图标上。
-
展开 Trip.jsp 图标并从 Hyperlink 组件中拖动一个连接器到 Page1.jsp 图标上。下图将显示页面导航设置。
图 9:页面导航 |
运行应用程序。在 Home 页面中,展开某位旅行者的姓名并单击一个旅行日期。
此时将打开包含该行程详细信息的 Trip 页面,如下图所示。
 图 10:运行时的详细信息页面 |
- 在 Trip 页面中,单击 Home 链接。我们发现在 Home 页面中,上次所选择行程的第一级节点仍然是展开的。
- 通过展开、合并第一级 Tree 节点和单击行程日期继续研究应用程序。
更多功能:将动作方法绑到 Tree 节点
如果使用的是 JavaServer Faces 1.2 Tree 组件(也就是说,项目的 Java EE Version 设置为 Java EE 5 平台),那么可以将运行方法绑定到树节点,并且可以在运行方法中调用 Tree 组件的 getSelected() 方法来确定所单击的节点,如以下步骤所示。
- 添加一个 tripId 属性到 Integer 类型的 Request Bean 中。
将以下方法添加到页面 1 的 Java 源代码中:
| 代码示例 4:页面 1 的 tripNode_action 方法 |
public String tripNode_action() {
// Get the id of the currently selected tree node
String nodeId = displayTree.getSelected();
// Find the tree node component with the given id
TreeNode selectedNode =
(TreeNode) this.getForm1().findComponentById(nodeId);
try {
// Node's id property is composed of "trip" plus the trip id
// Extract the trip id and save it for the next page
Integer tripId = Integer.valueOf(selectedNode.getId().substring(4));
getRequestBean1().setTripId(tripId);
} catch (Exception e) {
error("Can't convert node id to Integer: " +
selectedNode.getId().substring(4));
return null;
}
return "case1";
}
|
- 在页面 1 的
prerender 方法中,将以下代码替换为代码示例 5 中的代码。 tripNode.setUrl("/faces/Trip.jsp?tripId=" +
tripDataProvider.getValue("TRIP.TRIPID").toString());
| 代码示例 5:调整 the prerender 方法 |
ExpressionFactory exFactory =
getApplication().getExpressionFactory();
ELContext elContext =
getFacesContext().getELContext();
tripNode.setActionExpression(
exFactory.createMethodExpression(
elContext, "#{Page1.tripNode_action}",
String.class, new Class<?>[0])); |
- 按下 Alt-Shift-F 组合键修复导入。
-
在页面 1 的 prerender() 方法中,将其主体替换以下代码。
| 代码示例 6:Trip 页面的 prerender 方法 |
public void prerender() {
Integer tripId = getRequestBean1().getTripId();
try {
getSessionBean1().getTripRowSet1().setObject(1, tripId);
tripDataProvider1.refresh();
} catch (Exception e) {
error("Cannot display trip " + tripId);
log("Cannot display trip " + tripId, e);
}
}
|
- 运行应用程序。
关于树节点选择的注意事项
如果您的项目为 J2EE 1.4 项目,那么在选择树节点时应该注意以下一些事项:
- JavaServer Faces 1.1 Tree 组件不能使用
getSelected 方法或 getCookieSelectedTreeNode 方法来确定所选择的节点。如果用户关闭了浏览器 cokkies,则这些方法将不会返回正确的值。另外,对于开启 cookies 的浏览器,用户第一次访问页面并单击某个节点时,cokkie 可能会返回错误值。如果 cookies 中保存了上次访问的信息,那么可能会返回之前选择的值。由于 JavaServer Faces 1.2 版本的 Tree 组件没有使用 cookies 保存选择值,因此 1.2 版本中不存在此问题。
- Tree 节点的突出显示并不会在会话之间清除。如果不止一次运行本教程中的程序,则上次会话中选择的节点将在页面刚打开时突出显示在新会话中。出现此问题的原因是程序使用 cookies 传递所选择节点的 ID。
结束语
在本教程中,我们通过数据库中的数据构建了一个树型结构。我们构建了一个由两个页面组成的应用程序,它的第一个页面包含一个 Tree 组件。我们在 Tree 组件的第一级节点中填充了数据库中的人物姓名,并在第二级节点中填充了该人物的旅程信息。我们将第一个页面中的旅程节点与第二个页面链接到一起,第二个页面显示了该旅程的详细停息。
更多信息
This page was last modified: December 3, 2007