一、范例查询 我们的终极目标是一个能够满足所有潜在用户的Intranet。为此,我们必须提高Intranet用户访问数据库的灵活性,一种可能的方案是采用所谓的即席查询(Ad-hoc Query)。 “即席”两个字在这里的含义是“不作非凡预备地,随意、自由地”。即席查询答应用户象数据库治理员一样,自由地访问数据库。也许,最灵活的方式是让用户在Web页面的文本输入框中直接输入SQL命令,然后由应用发送该SQL命令查询数据库。然而,虽然这种方式很灵活,但要实施得好很困难,存在许多问题。 首先,这种方式不安全。假如不对用户进行大量的培训,不在应用中对用户输入的SQL命令进行严格的检验,用户可能有意无意地破坏系统运行。另外,即使进行了培训,要求用户总是能够构造出高效的SQL查询也不切实际。 然而,这些问题并不能完全阻碍我们构造出有效的Intranet即席查询系统。一般地,Intranet内的用户比网络之外的用户可信度高。为此,我们可以采用灵活性稍差但仍不失高效的方案——范例查询(Query-By-Example,QBE)。范例查询的使用简单、灵活,不需要对用户进行大量的培训,同时它也比直接使用SQL的方式更安全。 在范例查询系统中,我们提供给用户的界面与数据库结构之间有着密切的对应关系。每一个查询项目有一个相应的用户界面控件。例如,假设有一个雇员信息数据库,我们用一个列表框答应用户选择雇员所在的部门,用一个文本框答应用户输入薪金范围限制查询结果。 二、数据库抽象 对于一些程序员来说,数据库操作有时就象是一堆散乱的连接字符串、SQL命令和结果集。Java的面向对象特色可以让数据源具有更好的可治理性。接下来我们将用Java技术构造一个浏览器界面的QBE系统。这个系统以几个核心类为基础,核心类答应jsp页面在更高的层次上操作数据库,避免大量地编写底层SQL代码。 数据库最基本的元素之一是表。在数据库中,表是数据记录的容器,比如用来容纳雇员名字和薪水信息。下面的DBTable类描述的就是数据库里面的表。DBTable类的公用方法负责处理最底层的细节。比如,addChildTable方法用来建立表的父-子关系,addConstraint方法用来过滤表的输出。 【Listing 1:DBTable.java,描述数据库的表】 import java.util.*; public class DBTable { String pkey; // 主键 String name; // 表的名字 Vector columns; // 结果集包含的列 Hashtable col_desc; // 各个列的描述 Vector children; // 子表 Vector constraints; // 所有约束 /* 创建一个新的、未经初始化的表*/ protected DBTable() { columns = new Vector(); children = new Vector(); constraints = new Vector(); col_desc = new Hashtable(); } /* 创建一个新的表,指定名字和主键*/ public DBTable(String name, String pkey) { this(); this.name = name; this.pkey = pkey; } /* 返回主键 */ public String getPrimaryKey() { return pkey; } /* 创建一个新的约束,设置它的值, * 并把它加入表的约束列表 */ public void addConstraint(String column, int op, String value) { Constraint c = new Constraint(); c.column = column; c.op = op; c.value = value; constraints.add(c); } /* 把结果集限制为单个记录的简便方法 */ public void constrainByPrimaryKey(String value) { addConstraint(pkey, Constraint.EQ, "'" + value + "'"); } /* 添加一个列 */ public void addColumn(String column, String description) { columns.add(column); col_desc.put(column, description); } /* 添加一个子表,通过外键建立关系 */ public void addChildTable(DBTable table, String fkey) { children.add(table); addConstraint(this.pkey, Constraint.EQ, table.name + "." + fkey); } /* 搜索当前表以及(递归地搜索)所有子表, * 寻找指定的列,返回该列的描述 */ public String findColumnDescription(String column) { String result = (String) col_desc.get(column); if (result != null) { // 已经找到指定的列 return result; } else { // 在所有子表中搜索该列 Enumeration e = children.elements(); while (e.hasMoreElements()) { DBTable child = (DBTable) e.nextElement(); result = child.findColumnDescription(column); if (result != null) { // 已经找到! return result; } } return null; // 在所有表中都无法找到指定的列 } } /* 搜索当前表以及(递归地搜索)所有子表, * 检查指定的列是否是一个主键 */ public boolean isPrimaryKey(String column) { if (pkey.equals(column)) { // 已经找到指定的列 return true; } else { // 搜索所有子表 Enumeration e = children.elements(); while (e.hasMoreElements()) { DBTable child = (DBTable) e.nextElement(); if (child.isPrimaryKey(column)) { // 已经找到! return true; } } return false; } } } 单独的DBTable类其实没有什么实际用途,它只是一种抽象的描述,既没有建立底层的数据库连接,也没有任何SQL命令。为发挥DBTable类的作用,我们必须定义一个DBTable类的子类,在子类中利用DBTable类定义的方法访问数据库服务器。 下面就是DBTable类的子类SQLDBTable.java: 【Listing 2:SQLDBTable,扩展DBTable提供SQL和JDBC支持】 import java.sql.*; import java.util.*; public class SQLDBTable extends DBTable { /* 构造函数 */ public SQLDBTable(String name, String pkey) { super(name, pkey); } /* 生成一个SQL命令 */ public String generateSQL() { /* 获得SQL命令中出现的所有表的一个清单 */ Vector tables = new Vector(); findTables(tables); /* 获得所有列的一个清单 */ Vector columns = new Vector(); findColumns(columns); /* 获得必须在WHERE子句中出现的所有约束 * 的一个清单 */ Vector where = new Vector(); findConstraints(where); /* 创建一个容纳SQL命令的StringBuffer */ StringBuffer sql = new StringBuffer("SELECT "); sql.append(delimitedList(", ", columns.elements())); sql.append(" FROM "); sql.append(delimitedList(", ", tables.elements())); if (where.size() > 0) { sql.append(" WHERE "); sql.append(delimitedList(" AND ", where.elements())); } return sql.toString(); } /* 利用一个JDBC连接提取结果记录, * 注重:调用者必须关闭结果集的 * Statement对象 */ public ResultSet fetchRows(Connection conn) throws Exception { String sql = generateSQL(); /* 创建Statement(可能抛出SQLExeception异常)*/ Statement stmt = conn.createStatement(); /* 返回结果集 */ return stmt.executeQuery(sql); } /* 这是一个从主键以外的各个列获取数据的简便 * 方法。它在下列情形下使用:当你不想让用户看到主键时。 *关于使用该方法的例子,请参见HtmlSelectListMaker.java。 */ public Enumeration getDisplayData(ResultSet rs) throws SQLException { ResultSetMetaData rmd = rs.getMetaData(); Vector result = new Vector(); for(int i = 0; i < rmd.getColumnCount(); i++) { String column = rmd.getColumnName(i + 1); if (isPrimaryKey(column)) { continue; } result.addElement( rs.getString(column) ); } return result.elements(); } /* 该方法生成带分界符的列表,仅供内部使用。 * 用来为SQL命令生成空格或逗号分隔的列表。 */ private String delimitedList(String delim, Enumeration e)
|