Vincent

          Vicent's blog
          隨筆 - 74, 文章 - 0, 評論 - 5, 引用 - 0
          數(shù)據(jù)加載中……

          JDBC 查詢?nèi)罩咀兊煤唵?/a>

          JDBC java.sql.PreparedStatement接口的簡單擴展可以使查詢記錄更少犯錯,同時整理您的代碼。在本文中,IBM電子商務(wù)顧問Jens Wyke向您介紹如何應(yīng)用基本的封裝技術(shù)(“通過封裝來實現(xiàn)擴展”也稱為Decorator設(shè)計模式)來獲得最滿意的結(jié)果。

          在大多數(shù)情況下,JDBC PreparedStatements 使執(zhí)行數(shù)據(jù)庫查詢更簡便并可以顯著提升您整體應(yīng)用程序的性能。當(dāng)談到日志查詢語句時 PreparedStatement 接口就顯得有些不足了。 PreparedStatement 的優(yōu)勢在于其可變性,但是一個好的日志條目必須正確描述如何將SQL發(fā)送到數(shù)據(jù)庫,它將密切關(guān)注用實際的參數(shù)值來替換所有參數(shù)占位符。雖然有多種方法可以解決這一難題,但沒有任何一種易于大規(guī)模實施并且大部分將擾亂您的程序代碼。

          在本文中,您將了解到如何擴展JDBC PreparedStatement 接口來進(jìn)行查詢?nèi)罩尽?LoggableStatement 類實現(xiàn) PreparedStatement 接口,但添加用于獲得查詢字符串的方法,使用一種適用于記錄的格式。使用 LoggableStatement 類可以減少日志代碼中發(fā)生錯誤的幾率,生成簡單且易于管理的代碼。

          注意:本文假設(shè)您有豐富的JDBC和 PreparedStatement 類經(jīng)驗。

          典型日志解決方案

          表1介紹了數(shù)據(jù)庫查詢時通常是如何使用 PreparedStatement (雖然忽略了初始化和錯誤處理)。在本文中,我們將使用SQL query SELECT 做為例子,但討論使用其它類型的SQL語句,如 DELETEUPDATEINSERT


          表1:一個典型的SQL數(shù)據(jù)庫查詢
          												
          														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中一個好的查詢?nèi)罩緱l目看起來應(yīng)與下面有幾分類似:

          												
          														Executing query: select foo,bar from foobar where foo < 99 and 
          bar='christmas'
          
          												
          										

          下面是查詢的日志代碼的一個例子。注意:表1中的問號已經(jīng)被每個參數(shù)的值替換。

          												
          														System.out.println("Executing query: select foo, bar from foobar where foo
          < "+fooValue+" and bar = '+barValue+"'")
          
          												
          										

          一種更好的方法是創(chuàng)建方法,我們稱之為 replaceFirstQuestionMark ,它讀取查詢字符串并用參數(shù)值替換問號,如表2所示。這類方法的使用無需創(chuàng)建復(fù)制的字符串來描述SQL語句。


          表 2:使用replaceFirstQuestionMark來進(jìn)行字符串替換
          												
          														      // listing 1 goes here
          
               sql = replaceFirstQuestionMark(sql, fooValue);
               sql = replaceFirstQuestionMark(sql, barValue);
               System.out.println("Executing query: "+sql);
          
          												
          										

          雖然這些解決方案都易于實施,但沒有一種是完美的。問題是在更改SQL模板的同時也必須更改日志代碼。您將在某一點上犯錯幾乎是不可避免的。查詢將更改但您忘記了更新日志代碼,您將結(jié)束與將發(fā)送到數(shù)據(jù)庫的查詢不匹配的日志條目 -- 調(diào)試惡夢。

          我們真正需要的是一種使我們能夠一次性使用每個參數(shù)變量(在我們的實例中為 fooValuebarValue )的設(shè)計方案。我們希望有一種方法,它使我們能夠獲得查詢字符串,并用實際的參數(shù)值替換參數(shù)占位符。由于 java.sql.PreparedStatement 沒有此類方法,我們必須自己實現(xiàn)。





          回頁首


          定制解決方案

          我們的 PreparedStatement 定制實施將做為圍繞JDBC驅(qū)動器提供的“真實語句(real statement)”的封裝器(Wrapper)。封裝器語句將轉(zhuǎn)發(fā)所有方法調(diào)用(例如 setLong(int, long)setString(int,String) ) 到“真實語句”。在這樣做之前它將保存相關(guān)的參數(shù)值,從而它們可以用于生成日志輸出結(jié)果。

          表3介紹了 LoggableStatement 類如何實現(xiàn) java.sql.PreparedStatement ,以及它如何使用JDBC連接和SQL模板作為輸入來構(gòu)建。


          表3:LoggableStatement實現(xiàn)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() 方法添加一個調(diào)用,以及在方法 setLongsetString 的“真實語句”上調(diào)用相應(yīng)的方法。我們采用與用于參數(shù)設(shè)置的所有方法(例如 setCharsetLongsetRefsetObj )相同的方式來增加 saveQueryParamValue() 調(diào)用。表4還顯示了在不調(diào)用 saveQueryParamValue() 的情況下如何封裝方法 executeQuery ,因為它不是一個“參數(shù)設(shè)置”方法。


          表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() 方法。它把每個參數(shù)值轉(zhuǎn)換成 String 表示,保存以便 getQueryString 方法日后使用。缺省情況下,一個對象使用其 toString 方法將被轉(zhuǎn)換成 String ,但如果對象是 StringDate ,它將用單引號('')表示。 getQueryString() 方法使您能夠從日志復(fù)制大多數(shù)查詢并進(jìn)行粘貼,無需修改交互式SQL處理器就可進(jìn)行測試和調(diào)試。您可以根據(jù)需要修訂該方法來轉(zhuǎn)換其它類的參數(shù)值。


          表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);
           }
          
          												
          										

          當(dāng)我們使用標(biāo)準(zhǔn)方法來設(shè)置所有參數(shù)時,我們在 LoggableStatement 中簡單調(diào)用 getQueryString() 方法來獲得查詢字符串。所有問號都將被真正的參數(shù)值替換,它準(zhǔn)備輸出到我們選定的日志目的地。





          回頁首


          使用LoggableStatement

          表6顯示如何更改表1和表2中的代碼來使用 LoggableStatement 。將 LoggableStatement 引入到我們的應(yīng)用程序代碼中可以解決復(fù)制的參數(shù)變量問題。如果改變了SQL模板,我們只需更新 PreparedStatement 上的參數(shù)設(shè)置調(diào)用(例如添加一個 pstmt.setString(3,"new-param-value") )。這一更改將在日志輸出結(jié)果中反映出,無需任何記錄代碼的手工更新。


          表6:使用LoggableStatement&#160
          												
          														    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();
          
          												
          										





          回頁首


          結(jié)束語

          使用本文介紹的非常簡單的步驟,您可以為查詢記錄擴展JDBC PreparedStatement 接口。我們在此處使用的技術(shù)可以被視為“通過封裝來實現(xiàn)擴展”,或作為Decorator設(shè)計模式的一個實例(見 參考資料)。通過封裝來實現(xiàn)擴展在當(dāng)您必須擴展API但subclassing不是一項可選功能時極其有用。

          posted on 2006-08-24 17:50 Binary 閱讀(248) 評論(0)  編輯  收藏 所屬分類: j2se

          主站蜘蛛池模板: 仙游县| 库尔勒市| 和平区| 南京市| 桃园市| 永定县| 中方县| 盐亭县| 崇阳县| 余庆县| 泉州市| 左云县| 库尔勒市| 驻马店市| 广昌县| 湘乡市| 凉城县| 桐梓县| 沁水县| 阆中市| 太仆寺旗| 梁山县| 庆城县| 保亭| 高阳县| 滁州市| 邵东县| 罗江县| 资源县| 洛扎县| 宜兰市| 塔城市| 峨眉山市| 海晏县| 开阳县| 巢湖市| 灌云县| 伊通| 政和县| 清苑县| 眉山市|