分享
 
 
 

让JDBC查询日志变得简单

王朝other·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

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应用程序整体性能的措施。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有