讓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應用程序整體性能的措施。