`
isiqi
  • 浏览: 16031256 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

让JDBC查询日志变得简单

阅读更多

  JDBC java.sql.PreparedStatement接口的简单扩展可以使查询日志更少犯错,同时整理您的代码。在本文中,IBM电子商务顾问Jens Wyke向您介绍如何应用基本的封装技术(“通过封装来实现扩展”也称为Decorator设计模式)来获得最满意的结果。

  在大多数情况下,JDBC PreparedStatements 使执行数据库查询更简便并可以显著提升您整体应用程序的性能。当谈到日志查询语句时PreparedStatement接口就显得有些不足了。PreparedStatement的优势在于其可变性,但是一个好的日志条目必须正确描述如何将SQL发送到数据库,它将密切关注用实际的参数值来替换所有参数占位符。虽然有多种方法可以解决这一难题,但没有任何一种易于大规模实施并且大部分将扰乱您的程序代码。

  在本文中,您将了解到如何扩展JDBC PreparedStatement接口来进行查询日志。LoggableStatement类实现PreparedStatement接口,但添加用于获得查询字符串的方法,使用一种适用于记录的格式。使用LoggableStatement类可以减少日志代码中发生错误的几率,生成简单且易于管理的代码。

  注意:本文假设您有丰富的JDBC和PreparedStatement类经验。

  典型日志解决方案  

  表1介绍了数据库查询时通常是如何使用PreparedStatement(虽然忽略了初始化和错误处理)。在本文中,我们将使用SQL query SELECT做为例子,但讨论使用其它类型的SQL语句,如DELETE、UPDATE和INSERT。

  表1:一个典型的SQL数据库查询

<ccid_nobr></ccid_nobr>

<ccid_code><font size="4"><font color="#a52a2a">String sql = "select foo, bar from foobar where foo &lt; ? and bar = ?";
    String fooValue = new Long(99);
    String barValue = "christmas";

    Connection conn = dataSource.getConnection();
    PreparedStatement pstmt = conn.prepareStatement(sql);

    pstmt.setLong(1,fooValue);
    pstmt.setString(2,barValue);

    ResultSet rs = pstmt.executeQuery();

    // parse result...</font></font></ccid_code>


  表1中一个好的查询日志条目看起来应与下面有几分类似:

<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">Executing query: select foo,bar from foobar where foo &lt; 99 and 
bar='christmas'</font></font></ccid_code>


  下面是查询的日志代码的一个例子。注意:表1中的问号已经被每个参数的值替换。

<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">System.out.println("Executing query:
  select foo, bar from foobar where foo
&lt; "+fooValue+" and bar = '+barValue+"'")</font></font></ccid_code>


  一种更好的方法是创建方法,我们称之为replaceFirstQuestionMark,它读取查询字符串并用参数值替换问号,如表2所示。这类方法的使用无需创建复制的字符串来描述SQL语句。

  表 2:使用replaceFirstQuestionMark来进行字符串替换

<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">// listing 1 goes here

     sql = replaceFirstQuestionMark(sql, fooValue);
     sql = replaceFirstQuestionMark(sql, barValue);
     System.out.println("Executing query: "+sql);</font></font></ccid_code>


  虽然这些解决方案都易于实施,但没有一种是完美的。问题是在更改SQL模板的同时也必须更改日志代码。您将在某一点上犯错几乎是不可避免的。查询将更改但您忘记了更新日志代码,您将结束与将发送到数据库的查询不匹配的日志条目 -- 调试恶梦。

  我们真正需要的是一种使我们能够一次性使用每个参数变量(在我们的实例中为fooValue和barValue)的设计方案。我们希望有一种方法,它使我们能够获得查询字符串,并用实际的参数值替换参数占位符。由于java.sql.PreparedStatement没有此类方法,我们必须自己实现。

  定制解决方案

  我们的PreparedStatement定制实施将做为围绕JDBC驱动器提供的“真实语句(real statement)”的封装器(Wrapper)。封装器语句将转发所有方法调用(例如setLong(int, long)和setString(int,String)) 到“真实语句”。在这样做之前它将保存相关的参数值,从而它们可以用于生成日志输出结果。

  表3介绍了LoggableStatement类如何实现java.sql.PreparedStatement,以及它如何使用JDBC连接和SQL模板作为输入来构建。

  表3:LoggableStatement实现java.sql.PreparedStatement

<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">public class LoggableStatement implements java.sql.PreparedStatement {

     // used for storing parameter values needed
      // for producing log
     private ArrayList parameterValues;     
          
     // the query string with question marks as  
     // parameter placeholders
     private String sqlTemplate;       
               
     // a statement created from a real database     
     // connection                                       
     private PreparedStatement wrappedStatement; 
                                                 

    public LoggableStatement(Connection connection, String sql) 
      throws SQLException {
      // use connection to make a prepared statement
      wrappedStatement = connection.prepareStatement(sql);
      sqlTemplate = sql;
      parameterValues = new ArrayList();
    }
     }</font></font></ccid_code>


  LoggableStatement如何工作

  表4介绍了LoggableStatement如何向saveQueryParamValue()方法添加一个调用,以及在方法setLong和setString的“真实语句”上调用相应的方法。我们采用与用于参数设置的所有方法(例如setChar、setLong、setRef和setObj)相同的方式来增加saveQueryParamValue()调用。表4还显示了在不调用saveQueryParamValue()的情况下如何封装方法executeQuery,因为它不是一个“参数设置”方法。

  表4:LoggableStatement 方法

<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">public void setLong(int parameterIndex, long x) 
         throws java.sql.SQLException {
      wrappedStatement.setLong(parameterIndex, x);
      saveQueryParamValue(parameterIndex, new Long(x));
   }

   public void setString(int parameterIndex, String x) 
       throws java.sql.SQLException {
      wrappedStatement.setString(parameterIndex, x);
      saveQueryParamValue(parameterIndex, x);
   }

  public ResultSet executeQuery() throws java.sql.SQLException {
     return wrappedStatement.executeQuery();
   }</font></font></ccid_code>


  表5中显示了saveQueryParamValue()方法。它把每个参数值转换成String表示,保存以便getQueryString方法日后使用。缺省情况下,一个对象使用其 toString方法将被转换成String,但如果对象是String或Date,它将用单引号('')表示。getQueryString()方法使您能够从日志复制大多数查询并进行粘贴,无需修改交互式SQL处理器就可进行测试和调试。您可以根据需要修订该方法来转换其它类的参数值。

  表5:saveQueryParamValue()方法

<ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">private void saveQueryParamValue(int position, Object obj) {
      String strValue;
      if (obj instanceof String || obj instanceof Date) {
           // if we have a String, include '' in the saved value
           strValue = "'" + obj + "'";
      } else {
           if (obj == null) {
                // convert null to the string null
                 strValue = "null";
           } else {
                // unknown object (includes all Numbers), just call toString
                strValue = obj.toString();
           }
      }
      // if we are setting a position larger than current size of 
      // parameterValues, first make it larger
      while (position &gt;= parameterValues.size()) {
           parameterValues.add(null);
      }
      // save the parameter
      parameterValues.set(position, strValue);
 }</font></font></ccid_code>


  当我们使用标准方法来设置所有参数时,我们在LoggableStatement中简单调用getQueryString()方法来获得查询字符串。所有问号都将被真正的参数值替换,它准备输出到我们选定的日志目的地。

  使用LoggableStatement

  表6显示如何更改表1和表2中的代码来使用 LoggableStatement。将LoggableStatement引入到我们的应用程序代码中可以解决复制的参数变量问题。如果改变了SQL模板,我们只需更新PreparedStatement上的参数设置调用(例如添加一个pstmt.setString(3,"new-param-value"))。这一更改将在日志输出结果中反映出,无需任何记录代码的手工更新。

  表6:使用LoggableStatement

  <ccid_nobr></ccid_nobr>
<ccid_code><font size="4"><font color="#a52a2a">String sql = "select foo, bar from foobar where foo &lt; ? and bar = ?";
    long fooValue = 99;
    String barValue = "christmas";

    Connection conn = dataSource.getConnection();
    PreparedStatement pstmt;

    if(logEnabled) // use a switch to toggle logging.
        pstmt = new LoggableStatement(conn,sql);
    else
        pstmt = conn.prepareStatement(sql);

    pstmt.setLong(1,fooValue);
    pstmt.setString(2,barValue);

    if(logEnabled)
       System.out.println("Executing query: "+
         ((LoggableStatement)pstmt).getQueryString());

    ResultSet rs = pstmt.executeQuery();</font></font></ccid_code>


  结束语

  使用本文介绍的非常简单的步骤,您可以为查询记录扩展JDBC PreparedStatement接口。我们在此处使用的技术可以被视为“通过封装来实现扩展”,或作为Decorator设计模式的一个实例(见参考资料)。通过封装来实现扩展在当您必须扩展API但subclassing不是一项可选功能时极其有用。

  您将在参考资料部分找到LoggableStatement类的源代码。您可以按原样使用它,或者进行定制以满足您的数据库应用程序的特殊需求。

  参考资料

  ◆ 下载LoggableStatement类的源代码

  ◆ 您将在Roman Vichr的提示和技巧:JDBC 提示 (developerWorks, 2002年10月)中找到使用PreparedStatements的简要介绍.

  ◆ Lennart Jorelid的“Use JDBC for industrial-strength performance ”(developerWorks,2000年1月)是一篇不错的在JDBC中设计模式的两部分介绍性文章。

  ◆ Josh Heidebrecht撰写的“JDBC 3.0新特性 ”(developerWorks,2001年7月)提供JDBC 3.0的概述。

  ◆ 您可以从java.sun.com 下载Java platform, Standard Edition和JDBC 3.0 API规范。

  ◆ David Gallardo的“Java设计模式101 ”(developerWorks,2002年1月)是Gang of Four模板不错的介绍。

  ◆ Paul Monday的“ Java设计模式 201”(developerWorks,2002年4月)为高级学员提供了Java设计模式更具概念性的说明。

  ◆ Vince Huston的 Design Patterns site 是另外一个了解设计模式不错的资源。

  ◆ Brian Goetz的“Java 理论与实践:性能管理 — 您有规划吗? ” (developerWorks,2003年3月)阐述了一些您可以实施用来提升Java应用程序整体性能的措施。
分享到:
评论

相关推荐

    web站点崩溃的原因大全

    有许多种原因可能导致Web站点无法正常工作,这使得系统地检查所有问题变得很困难。下面将集中分析总结导致Web站点崩溃的最常见的问题。如果可以解决这些常规问题,那么也将有能力对付出现的一些意外情况。 磁盘已满 ...

    基于maven项目的SSM框架与layu前端框架的代码

    Spring MVC属于Spring Framework的后续产品,已经融合在Spring Web Flow里面,它原生支持的Spring特性,让开发变得非常简单规范。Spring MVC 分离了控制器、模型对象、分派器以及处理程序对象的角色,这种分离让...

    urban_transport_orel:城市交通系统。 公交,无轨电车或有轨电车已分配给该路线。 车辆必须按照特定的间隔或时间表行驶

    使创建基于Spring的,生产级的应用程序和服务变得容易,而不必大惊小怪。 -除了Java语言之外,Spring还提供了创建企业应用程序所需的一切。 适用于Web和独立环境的现代服务器端Java模板引擎。 -Java的日志记录系统...

    支持多数据库的ORM框架ef-orm.zip

    基于轻量级Criteria API的操作方式,使得对数据库的变更和重构变得非常轻松,解决了SQL语句多对软件维护和移植造成产生的不利影响。 阅读推荐:第3、4章 特点二,将SQL的使用发挥到极致,解决SQL拼凑问题、...

    Java测试新技术TestNG和高级概念.part1

    无论您使用TestNG,JUnit或其他测试框架,本书提供的测试设计模式都会告诉您如何改进您的测试,对如何让代码和设计变得更可测试提供具体的建议。 目录: 第1章 起步 1.1 超越JUnit 3 1.2 JUnit 4 1.3 针对可测试性而...

    Java测试新技术TestNG和高级概念.part2

    无论您使用TestNG,JUnit或其他测试框架,本书提供的测试设计模式都会告诉您如何改进您的测试,对如何让代码和设计变得更可测试提供具体的建议。 目录: 第1章 起步 1.1 超越JUnit 3 1.2 JUnit 4 1.3 针对可测试性而...

    JAVA上百实例源码以及开源项目源代码

    摘要:JAVA源码,媒体网络,山寨QQ,Java聊天程序 Java编写的山寨QQ,多人聊天+用户在线,程序分服务端和客户端,典型C/S结构, 当用户发送第一次请求的时候,验证用户登录,创建一个该qq号和服务器端保持通讯连接得线程...

    java开源包1

    GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet....

    java开源包11

    GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet....

    java开源包2

    GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet....

    java开源包3

    GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet....

    java开源包6

    GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet....

    java开源包5

    GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet....

    java开源包10

    GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet....

    java开源包4

    GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet....

    java开源包8

    GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet....

    java开源包7

    GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet....

    java开源包9

    GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet....

    java开源包101

    GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet....

    Java资源包01

    GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet....

Global site tag (gtag.js) - Google Analytics