一范例查询
我们的终极目标是一个能够满足所有潜在用户的Intranet为此我们必须提高Intranet用户访问数据库的灵活性一种可能的方案是采用所谓的即席查询(Adhoc Query)
即席两个字在这里的含义是不作特殊准备地随意自由地即席查询允许用户象数据库管理员一样自由地访问数据库也许最灵活的方式是让用户在Web页面的文本输入框中直接输入SQL命令然后由应用发送该SQL命令查询数据库然而虽然这种方式很灵活但要实施得好很困难存在许多问题
首先这种方式不安全如果不对用户进行大量的培训不在应用中对用户输入的SQL命令进行严格的检验用户可能有意无意地破坏系统运行另外即使进行了培训要求用户总是能够构造出高效的SQL查询也不切实际
然而这些问题并不能完全阻碍我们构造出有效的Intranet即席查询系统一般地Intranet内的用户比网络之外的用户可信度高为此我们可以采用灵活性稍差但仍不失高效的方案——范例查询(QueryByExampleQBE)范例查询的使用简单灵活不需要对用户进行大量的培训同时它也比直接使用SQL的方式更安全
在范例查询系统中我们提供给用户的界面与数据库结构之间有着密切的对应关系每一个查询项目有一个相应的用户界面控件例如假设有一个雇员信息数据库我们用一个列表框允许用户选择雇员所在的部门用一个文本框允许用户输入薪金范围限制查询结果
二数据库抽象
对于一些程序员来说数据库操作有时就象是一堆散乱的连接字符串SQL命令和结果集Java的面向对象特色可以让数据源具有更好的可管理性接下来我们将用Java技术构造一个浏览器界面的QBE系统这个系统以几个核心类为基础核心类允许JSP页面在更高的层次上操作数据库避免大量地编写底层SQL代码
数据库最基本的元素之一是表在数据库中表是数据记录的容器比如用来容纳雇员名字和薪水信息下面的DBTable类描述的就是数据库里面的表DBTable类的公用方法负责处理最底层的细节比如addChildTable方法用来建立表的父子关系addConstraint方法用来过滤表的输出
【Listing DBTablejava描述数据库的表】
import javautil*;
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();
thisname = name;
thispkey = pkey;
}
/* 返回主键 */
public String getPrimaryKey() {
return pkey;
}
/* 创建一个新的约束设置它的值
* 并把它加入表的约束列表
*/
public void addConstraint(String column
int opString value) {
Constraint c = new Constraint();
lumn = column;
cop = op;
cvalue= value;
constraintsadd(c);
}
/* 把结果集限制为单个记录的简便方法 */
public void constrainByPrimaryKey(String value) {
addConstraint(pkey ConstraintEQ + value + );
}
/* 添加一个列 */
public void addColumn(String column
String description) {
columnsadd(column);
col_descput(column description);
}
/* 添加一个子表通过外键建立关系 */
public void addChildTable(DBTable table String fkey) {
childrenadd(table);
addConstraint(thispkey ConstraintEQ
tablename + + fkey);
}
/* 搜索当前表以及(递归地搜索)所有子表
* 寻找指定的列返回该列的描述 */
public String findColumnDescription(String column) {
String result = (String) col_descget(column);
if (result != null) { // 已经找到指定的列
return result;
} else {
// 在所有子表中搜索该列
Enumeration e = childrenelements();
while (ehasMoreElements()) {
DBTable child = (DBTable) enextElement();
result = childfindColumnDescription(column);
if (result != null) { // 已经找到!
return result;
}
}
return null; // 在所有表中都无法找到指定的列
}
}
/* 搜索当前表以及(递归地搜索)所有子表
* 检查指定的列是否是一个主键 */
public boolean isPrimaryKey(String column) {
if (pkeyequals(column)) { // 已经找到指定的列
return true;
} else {
// 搜索所有子表
Enumeration e = childrenelements();
while (ehasMoreElements()) {
DBTable child = (DBTable) enextElement();
if (childisPrimaryKey(column)) { // 已经找到!
return true;
}
}
return false;
}
}
}
单独的DBTable类其实没有什么实际用途它只是一种抽象的描述既没有建立底层的数据库连接也没有任何SQL命令为发挥DBTable类的作用我们必须定义一个DBTable类的子类在子类中利用DBTable类定义的方法访问数据库服务器
下面就是DBTable类的子类SQLDBTablejava
【Listing SQLDBTable扩展DBTable提供SQL和JDBC支持】
import javasql*;
import javautil*;
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 );
sqlappend(delimitedList( columnselements()));
sqlappend( FROM );
sqlappend(delimitedList( tableselements()));
if (wheresize() > ) {
sqlappend( WHERE );
sqlappend(delimitedList( AND whereelements()));
}
return sqltoString();
}
/* 利用一个JDBC连接提取结果记录
* 注意调用者必须关闭结果集的
* Statement对象
*/
public ResultSet fetchRows(Connection conn)
throws Exception
{
String sql = generateSQL();
/* 创建Statement(可能抛出SQLExeception异常)*/
Statement stmt = conncreateStatement();
/* 返回结果集 */
return stmtexecuteQuery(sql);
}
/* 这是一个从主键以外的各个列获取数据的简便
* 方法它在下列情形下使用当你不想让用户看到主键时
*关于使用该方法的例子请参见HtmlSelectListMakerjava
*/
public Enumeration getDisplayData(ResultSet rs) throws SQLException
{
ResultSetMetaData rmd = rsgetMetaData();
Vector result = new Vector();
for(int i = ; i < rmdgetColumnCount(); i++) {
String column = rmdgetColumnName(i + );
if (isPrimaryKey(column)) {
continue;
}
resultaddElement( rsgetString(column) );
}
return resultelements();
}
/* 该方法生成带分界符的列表仅供内部使用
* 用来为SQL命令生成空格或逗号分隔的列表
*/
private String delimitedList(String delim Enumeration e)