让JDBC查询日志变得简单
2024-07-21 02:08:00
供稿:网友
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数据库查询
string sql = "select foo, bar from foobar where foo < ? 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...
表1中一个好的查询日志条目看起来应与下面有几分类似:
executing query: select foo,bar from foobar where foo < 99 and bar='christmas'
下面是查询的日志代码的一个例子。注意:表1中的问号已经被每个参数的值替换。
system.out.println("executing query: select foo, bar from foobar where foo< "+foovalue+" and bar = '+barvalue+"'")
一种更好的方法是创建方法,我们称之为replacefirstquestionmark,它读取查询字符串并用参数值替换问号,如表2所示。这类方法的使用无需创建复制的字符串来描述sql语句。
表 2:使用replacefirstquestionmark来进行字符串替换
// listing 1 goes here sql = replacefirstquestionmark(sql, foovalue); sql = replacefirstquestionmark(sql, barvalue); system.out.println("executing query: "+sql);
虽然这些解决方案都易于实施,但没有一种是完美的。问题是在更改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
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(); } }
loggablestatement如何工作
表4介绍了loggablestatement如何向savequeryparamvalue()方法添加一个调用,以及在方法setlong和setstring的“真实语句”上调用相应的方法。我们采用与用于参数设置的所有方法(例如setchar、setlong、setref和setobj)相同的方式来增加savequeryparamvalue()调用。表4还显示了在不调用savequeryparamvalue()的情况下如何封装方法executequery,因为它不是一个“参数设置”方法。
表4:loggablestatement 方法
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(); }
表5中显示了savequeryparamvalue()方法。它把每个参数值转换成string表示,保存以便getquerystring方法日后使用。缺省情况下,一个对象使用其 tostring方法将被转换成string,但如果对象是string或date,它将用单引号('')表示。getquerystring()方法使您能够从日志复制大多数查询并进行粘贴,无需修改交互式sql处理器就可进行测试和调试。您可以根据需要修订该方法来转换其它类的参数值。
表5:savequeryparamvalue()方法
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 >= parametervalues.size()) { parametervalues.add(null); } // save the parameter parametervalues.set(position, strvalue); }
当我们使用标准方法来设置所有参数时,我们在loggablestatement中简单调用getquerystring()方法来获得查询字符串。所有问号都将被真正的参数值替换,它准备输出到我们选定的日志目的地。
使用loggablestatement
表6显示如何更改表1和表2中的代码来使用 loggablestatement。将loggablestatement引入到我们的应用程序代码中可以解决复制的参数变量问题。如果改变了sql模板,我们只需更新preparedstatement上的参数设置调用(例如添加一个pstmt.setstring(3,"new-param-value"))。这一更改将在日志输出结果中反映出,无需任何记录代码的手工更新。
表6:使用loggablestatement
string sql = "select foo, bar from foobar where foo < ? 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();
结束语
使用本文介绍的非常简单的步骤,您可以为查询记录扩展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应用程序整体性能的措施。