当前位置导航:炫浪网>>网络学院>>编程开发>>JAVA教程>>Java进阶

用Java构造Intranet范例查询系统


  一、范例查询
  我们的终极目标是一个能够满足所有潜在用户的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)
相关内容
赞助商链接