下载
 Oracle JDeveloper
 
   标签
adf, java, 全部
 
产品徽标 从设计到实践全面了解 Oracle ADF 应用程序

作者:Chris Muir ACE 总监 和 Penny Cookson ACE

第 4 章 —“可以具备”需求:记录搜索条件

2009 年 5 月发表

 单击此处查看“从设计到实践全面了解 Oracle ADF 应用程序”描述和目录。

在本节中,我们 可以具备 的主要需求是记录用户输入的所有搜索条件以及返回的记录数。这使我们可以在将来看到我们搜索页面的利用程度或用户在使用中是否遇到困难。

同样,我们需要将这些需求分解为独立的可解决单元:

  1. 创建额外的记录表存储查询结果。
  2. 识别框架查询事件阻塞点方法以捕获查询结果。
  3. 通过 JDBC 例程将查询结果写入数据库查询记录表。
  4. 创建额外的记录表以存储查询搜索字段。
  5. 通过 JDBC 例程将搜索参数写入数据库搜索参数记录表。

创建额外的记录表存储查询结果。

每个会话都会有一个或多个查询/搜索,每次返回零个或者一个或多个记录。因此,我们希望在数据库中创建一个额外的记录表来记录这些事实。我们将再一次请我们的 DBA 在数据库中创建一个新表:

CREATE TABLE log_query
(log_query_id     NUMBER(10,0)   NOT NULL
,log_session_id   NUMBER(10,0)   NOT NULL 
,sql_statement    VARCHAR2(2000) NOT NULL
,records_returned NUMBER(3)      
,datetime         DATE           NOT NULL 
,CONSTRAINT log_query_pk PRIMARY KEY (log_query_id)
,CONSTRAINT log_query_fk1 FOREIGN KEY (log_session_id)
 REFERENCES log_session(log_session_id));

还有以下序列:

CREATE SEQUENCE log_query_seq;
注意该表如何让我们的 log_session 表主键的外键使我们可以针对用户会话记录查询。

识别框架查询事件阻塞点方法以捕获查询结果。

假设我们使用 log_query 表存储关于查询事件的信息,我们现在需要识别 Oracle ADF 框架中的一个阻塞点方法以捕获该事件,因此,我们可以向 log_query 表进行写入。

在 Oracle ADF 业务组件中的 ViewObjectImpl 中,executeQuery() 方法负责执行针对数据库的视图对象查询。我们可以在 ParcelViewImpl 中覆盖该方法,如下所示:

@Override
public void executeQuery() {
  super.executeQuery();
}

通过 JDBC 例程将查询结果写入数据库查询记录表。

现在我们已经具有 executeQuery() 方法,我们对 executeQuery() 方法进行增强以编写审计信息,如下所示:

@Override
public void executeQuery() {
  if (firstQuery) {
    super.executeQuery();
  } else {

    AppModuleImpl am = (AppModuleImpl)getApplicationModule();
     
                               am.insertLogQuery(getQuery(),null);

    super.executeQuery();
   
     
                               am.updateLogQuery(new Number(super.getQueryHitCount(getDefaultRowSetInternal())));
  }
}
                            
如您所见,我们希望先将查询记录到 log_query 表然后再执行查询,并在查询后,在同一记录中更新获取的记录数。在 AppModuleImpl 中,我们将定义 insertLogQuery() 和 updateLogQuery() 方法,如下所示:
public void insertLogQuery(String sqlStatement, Number recordsReturned) {
  DBTransaction trans = getDBTransaction();

  SequenceImpl seq = new SequenceImpl("LOG_QUERY_SEQ", trans);
   
                               setLogQueryId(seq.getSequenceNumber());

  CallableStatement statement = null;

  String plsql =
    "DECLARE "
   +  "PRAGMA AUTONOMOUS_TRANSACTION; "
   +"BEGIN "
   +  "INSERT INTO log_query "
   +  "(log_query_id, log_session_id, sql_statement, records_returned, datetime) "
   +  "VALUES (?,?,?,?,sysdate); "
   +  "COMMIT; "
   +"END;";

  statement = trans.createCallableStatement(plsql, 4);
  try {
    statement.setInt(1, getLogQueryId().intValue());
    statement.setInt(2, getLogSessionId().intValue());
    statement.setString(3, sqlStatement);
    if (recordsReturned == null)
      statement.setNull(4, Types.INTEGER);
    else
      statement.setInt(4, recordsReturned.intValue());

    int rows = statement.executeUpdate();

  } catch (SQLException s) {
    throw new JboException(s);
  } finally {
    try {
      if (statement != null)
        statement.close();
    } catch (SQLException s) { /* ignore */
    }
  }
}

public void updateLogQuery(Number recordsReturned) {
  DBTransaction trans = getDBTransaction();

  CallableStatement statement = null;

  String plsql =
    "DECLARE "
   +  "PRAGMA AUTONOMOUS_TRANSACTION; "
   +"BEGIN "
   +  "UPDATE log_query "
   +  "SET records_returned = ? "
   +  "WHERE log_query_id = ?; "
   +  "COMMIT; "
   +"END;";

  statement = trans.createCallableStatement(plsql, 2);
  try {
    if (recordsReturned == null)
      statement.setNull(1, Types.INTEGER);
    else
      statement.setInt(1, recordsReturned.intValue());           
       
                               statement.setInt(2, getLogQueryId().intValue());

    int rows = statement.executeUpdate();

  } catch (SQLException s) {
    throw new JboException(s);
  } finally {
    try {
      if (statement != null)
        statement.close();
    } catch (SQLException s) { /* ignore */
    }
  }
}
                            
您将看到,我们使用 setLogQueryId() 和 getLogQueryId() 方法写入和读取下一个 log_query_seq 值(从数据库到 AppModuleImpl 中的一个实例变量),与我们对 LogSessionId 的操作类似。我们将向 AppModuleImpl 添加以下代码:
private Number logQueryId;

public void setLogQueryId(Number logQueryId) {
  this.logQueryId = logQueryId;
}

public Number getLogQueryId() {
  return logQueryId;
}
与 LogSessionId 不同,我们无需担心能否确保正确钝化和激活 LogQueryId,因为这是在单一用户请求中使用 LogQueryId。

现在,您可以测试该功能。以下是 LOG_QUERY 表中记录条目的一个示例:

图 1

注意,该 SQL 语句包含绑定变量,实际上没有显示用户输入的查询条件。下一步将在新表中记录这些内容。

创建额外的记录表以存储查询搜索字段。

我们希望保存每次查询的搜索参数及其值。稍后,我们可以看见我们的用户正在进行何种搜索并结合 log_query 表记录找出他们是否成功返回了记录。因此,我们最后一次请我们的 DBA 在数据库中再创建一个记录表:

CREATE TABLE log_param
(log_param_id     NUMBER(10,0)   NOT NULL
,log_query_id     NUMBER(10,0)   NOT NULL
,param_name       VARCHAR2(100)  NOT NULL
,param_value      VARCHAR2(100)
,datetime         DATE           NOT NULL 
,CONSTRAINT log_param_pk PRIMARY KEY (log_param_id)
,CONSTRAINT log_param_fk1 FOREIGN KEY (log_query_id)
 REFERENCES log_query(log_query_id));
还有以下序列:
CREATE SEQUENCE log_param_seq;
注意该表如何让我们的 log_query 表主键的外键使我们可以针对用户会话查询跟踪参数。

识别框架阻塞点方法以捕获查询搜索参数并将其写入我们的记录表。

由于我们之前的工作,我们知道 ViewObjectImpl 有一个在执行视图对象的查询前调用的阻塞点方法 executeQueryForCollection()。这使我们可以查看用户输入的绑定变量。当前的 executeQueryForCollection() 方法如下所示:

@Override
protected void executeQueryForCollection(Object queryCollection, Object[] bindParams, int noUserParams) {
  if (firstQuery)
    firstQuery = false;
  else 
    enforceQueryCriteria();
  super.executeQueryForCollection(queryCollection, bindParams, noUserParams);
}

通过 JDBC 例程将搜索参数写入数据库搜索参数记录表。

由于我们已定义阻塞点方法,因此我们将添加对新方法 logQueryCriteria() 的调用,如下所示:

@Override
protected void executeQueryForCollection(Object queryCollection, Object[] bindParams, int noUserParams) {
  if (firstQuery)
    firstQuery = false;
  else {
     
                               logQueryCriteria(bindParams);
    enforceQueryCriteria();
  }
  super.executeQueryForCollection(queryCollection, bindParams, noUserParams);
}
                            
新方法 logQueryCriteria() 如下所示:
private void logQueryCriteria(Object[] bindParams) {
  String bindParamName;
  Object bindParamValue;
  
  AppModuleImpl am = (AppModuleImpl)getApplicationModule();

  for (int i = 0; i < bindParams.length; i++) {
    bindParamName = ((Object[])bindParams[i])[0].toString();
    bindParamValue = ((Object[])bindParams[i])[1];

    am.insertLogParam(bindParamName, bindParamValue);
  }
}
最后,我们将创建新的 AppModuleImpl 方法 insertLogParam() 以通过 JDBC 调用将查询参数插入到 log_param 表:
public void insertLogParam(String paramName, Object paramValue) {
  DBTransaction trans = getDBTransaction();

  SequenceImpl seq = new SequenceImpl("LOG_PARAM_SEQ", trans);

  CallableStatement statement = null;

  String plsql =
    "DECLARE "
   +  "PRAGMA AUTONOMOUS_TRANSACTION; "
   +"BEGIN "
   +  "INSERT INTO log_param "
   +  "(log_param_id, log_query_id, param_name, param_value, datetime) "
   +  "VALUES (?,?,?,?,sysdate); "
   +  "COMMIT; "
   +"END;";

  statement = trans.createCallableStatement(plsql, 4);
  try {
    statement.setInt(1, seq.getSequenceNumber().intValue());
    statement.setInt(2, getLogQueryId().intValue());
    statement.setString(3, paramName);

    if (paramValue == null) {
      statement.setNull(4, Types.VARCHAR);
    } else if (paramValue instanceof String) {
      if (((String)paramValue).equals(""))
        statement.setNull(4, Types.VARCHAR);
      else
        statement.setString(4, (String)paramValue);
    } else {
      statement.setString(4, paramValue.toString());
    }

    int rows = statement.executeUpdate();

  } catch (SQLException s) {
    throw new JboException(s);
  } finally {
    try {
      if (statement != null)
        statement.close();
    } catch (SQLException s) { /* ignore */
    }
  }
}
如果我们运行 SearchPage,无论接受还是拒绝条款和条件,用户信息都将写入到记录表中。

总结

在本节中,我们添加了代码以满足应用程序 可以具备 需求,包括记录用户输入的所有搜索条件以及返回的记录数。在下一章也是最后一章中,我们将介绍如何添加一些防止恶意访问的功能。  单击此处查看“从设计到实践全面了解 Oracle ADF 应用程序”描述和目录。