JDBC預編譯語句

cyzero發表於2013-02-28
什麼是預編譯語句
預編譯語句PreparedStatement 是java.sql中的一個介面,它是Statement的子介面。通過Statement物件執行SQL語句時,需要將SQL語句傳送給DBMS,由DBMS首先進行編譯後再執行。預編譯語句和Statement不同,在建立PreparedStatement 物件時就指定了SQL語句,該語句立即傳送給DBMS進行編譯。當該編譯語句被執行時,DBMS直接執行編譯後的SQL語句,而不需要像其他SQL語句那樣首先將其編譯。
 
什麼時候使用預編譯語句
一般是在需要反覆使用一個SQL語句時才使用預編譯語句,預編譯語句常常放在一個for或者while迴圈裡面使用,通過反覆設定引數從而多次使用該SQL語句;為了防止SQL隱碼攻擊漏洞,在某些資料操作中也使用預編譯語句。

JDBC使用預編譯SQL的好處


1. 執行效率

PreparedStatement可以儘可能的提高訪問資料庫的效能,資料庫在處理SQL語句時都有一個預編譯的過程,而預編譯物件就是把一些格式固定的SQL編譯後,存放在記憶體池中即資料庫緩衝池,當我們再次執行相同的SQL語句時就不需要預編譯的過程了,只需DBMS執行SQL語句。所以當你需要執行Statement物件多次的時候,PreparedStatement物件將會大大降低執行時間,特別是的大型的資料庫中,它可以有效的也加快了訪問資料庫的速度。


2. 程式碼可讀性、可維護性
比如向記錄使用者資訊的表中插入記錄: user(id, password, name, email, address)。使用Statement的SQ語句如下:
String sqlString ="insert into user values('"+
user.id + "', '"+
user.password + "', '" +
user.name + "', '" +
user.email + "', '" +
user.address + "')";

使用PrearedStatement的SQL語句如下:

String sqlString ="insert into user(id, password, name, email, address) values(?, ?, ?, ?, ?)";
PreparedStatement pstmt = connection.PreparedStatement(sqlString);
pstmt.setString(1, user.id);
pstmt.setString(2, user.password);
pstmt.setString(3, user.name);
pstmt.setString(4, user.email);
pstmt.setString(5, user.address);


使用佔位符?代替引數,將引數與SQL語句分離出來,這樣就可以方便對程式的更改和延續,同樣,也可以減少不必要的錯誤。


3. SQL執行的安全性
SQL隱碼攻擊:是從客戶端輸入一些非法的特殊字元,從而使伺服器端在構造SQL語句時仍能正確構造,從而收集程式和伺服器的資訊或資料。比如在Web資訊系統的登入入口處,要求使用者輸入使用者名稱和密碼,客戶端輸入後,伺服器端根據使用者輸入的資訊來構造SQL語句,在資料庫中查詢是否存在此使用者名稱以及密碼是否正確。假設使用上述例子中的表“user”構造SQL語句的Java程式可能是:
1
sqlString = "select * from user where user.id = '" + userID + "' and user.password = "' + userPassword + "'";
其中userID, userPassword是從使用者輸入的使用者名稱及密碼。如果使用者和密碼輸的都是 '1' or '1'='1',則伺服器端生成的SQL語句如下:
1
sqlString = "select * from user where user.id ='1'or'1'='1'and user.password ='1'or'1'='1'

這個SQL語句中的where字句沒有起到資料篩選的作用,因為只要資料庫中有記錄,就會返回一個結果不為空的記錄集,查詢就會通過。上面的例子說明:在Web環境中,有惡意的使用者會利用那些設計不完善的、不能正確處理字串的應用程式。特別是在公共Web站點上,在沒有首先通過PreparedStatement物件處理的情況下,所有的使用者輸入都不應該傳遞給SQL語句。此外,在使用者有機會修改SQL語句的地方,如HTML的隱藏區域或一個查詢字串上,SQL語句都不應該被顯示出來。


3. 減少硬解析,節約大量的CPU資源

使用預編譯可以利用資料庫的軟解析降低資源消耗,所謂軟解析,就是因為相同文字的SQL語句存在於library cache中,所以本次SQL語句的解析就可以去掉硬解析中的一個或多個步驟(主要為選擇執行計劃步驟),從而節省大量的資源的耗費。

關於軟解析和硬解析,可參見:http://blog.csdn.net/cyzero/article/details/8621171



附:

Statement,PreparedStatement 與 PreparedStatement + 批處理 的用法與比較

java方面

1.使用Statement物件

2.預編譯PreparedStatement

3.使用PreparedStatement + 批處理

為了區分出這三者之間的效率,下面的事例執行過程都是在資料庫表t1中插入1萬條記錄,並記錄出所需的時間(此時間與電腦硬體有關)


1.使用Statement物件

使用範圍:當執行相似SQL(結構相同,具體值不同)語句的次數比較少

優點:語法簡單

缺點:採用硬編碼效率低,安全性較差。

原理:硬編碼,每次執行時相似SQL都會進行編譯   

        

事例執行過程:

   public void exec(Connection conn){

           try {

                     Long beginTime = System.currentTimeMillis();

                           conn.setAutoCommit(false);//設定手動提交

                    Statement st = conn.createStatement();

                            for(int i=0;i<10000;i++){

                       String sql="insert into t1(id) values ("+i+")";

                       st.executeUpdate(sql); 

                    }

                           Long endTime = System.currentTimeMillis();

                   System.out.println("Statement用時:"+(endTime-beginTime)/1000+"秒");//計算時間

                   st.close();

                  conn.close();

                } catch (SQLException e) {               

                  e.printStackTrace();

          } 

   }

 

執行時間:Statement用時:31秒

 

2.預編譯PreparedStatement

使用範圍:當執行相似sql語句的次數比較多(例如使用者登陸,對錶頻繁操作..)語句一樣,只是具體的值不一樣,被稱為動態SQL

優點:語句只編譯一次,減少編譯次數。提高了安全性(阻止了SQL隱碼攻擊)

缺點: 執行非相似SQL語句時,速度較慢。

原理:相似SQL只編譯一次,減少編譯次數

名詞解釋:

SQL隱碼攻擊:select * from user where username="張三" and password="123" or 1=1;

前面這條語句紅色部分就是利用sql注入,使得這條詞句使終都會返回一條記錄,從而降低了安全性。

事例執行過程:

      public void exec2(Connection conn){

         try {

                   Long beginTime = System.currentTimeMillis();

                    conn.setAutoCommit(false);//手動提交

                    PreparedStatement pst = conn.prepareStatement("insert into t1(id) values (?)");

                    for(int i=0;i<10000;i++){

                       pst.setInt(1, i);

                       pst.execute();   

                    }

                      conn.commit();

                      Long endTime = System.currentTimeMillis();

                      System.out.println("Pst用時:"+(endTime-beginTime)+"秒");//計算時間

                      pst.close();

                      conn.close();

          } catch (SQLException e) {                 

                     e.printStackTrace();

          }

    }

執行時間:Pst用時:14秒

 

3.使用PreparedStatement + 批處理

使用範圍:一次需要更新資料庫表多條記錄

優點:減少和SQL引擎互動的次數,再次提高效率,相似語句只編譯一次,減少編譯次數。提高了安全性(阻止了SQL隱碼攻擊)

缺點:

原理:

批處理: 減少和SQL引擎互動的次數,一次傳遞給SQL引擎多條SQL。

名詞解釋:

PL/SQL引擎:在oracle中執行pl/sql程式碼的引擎,在執行中發現標準的sql會交給sql引擎進行處理。

SQL引擎:執行標準sql的引擎。

事例執行過程:

public void exec3(Connection conn){

                     try {

                      conn.setAutoCommit(false);

                      Long beginTime = System.currentTimeMillis();

                      PreparedStatement pst = conn.prepareStatement("insert into t1(id) values (?)");

                     

                      for(int i=1;i<=10000;i++){   

                       pst.setInt(1, i);

                       pst.addBatch();//加入批處理,進行打包

                       if(i%1000==0){//可以設定不同的大小;如50,100,500,1000等等

                        pst.executeBatch();

                        conn.commit();

                        pst.clearBatch();

                       }

                      }

                     pst.executeBatch();

                      Long endTime = System.currentTimeMillis();

                      System.out.println("pst+batch用時:"+(endTime-beginTime)+"毫秒");

                      pst.close();

                      conn.close();

                     } catch (SQLException e) {

                      // TODO Auto-generated catch block

                      e.printStackTrace();

                     }

 }

執行時間:pst+batch用時:485毫秒


 

資料庫方面


1 靜態SQL

使用範圍:

優點:每次執行相似sql都會進行編譯。

缺點:效率低,佔用破費cpu資源,耗費SGA 資源。

原理:(檢查SQL 語句在SGA 中的共享池中是否存在, 如果不存在,則編譯、解析後執行:硬解析,

     如果已存在,則取出編譯結果執行:軟解析)

事例執行過程:

DECLARE

 time1 NUMBER;

 time2 NUMBER;

BEGIN

  time1 := dbms_utility.get_time;

  EXECUTE IMMEDIATE 'truncate table t_demo';

  FOR i IN 1..10000

  LOOP

     EXECUTE IMMEDIATE

       'insert into t_demo(u_id) values('||i||')'; 

  END LOOP;

  time2 := dbms_utility.get_time;

  dbms_output.put_line((time2-time1)/100||'秒');

END

執行時間:pst+batch用時:20.93秒


2 動態SQL

使用範圍:

優點:語句只編譯一次,減少編譯次數,提高效率。

缺點:

原理:減少編譯次數(檢查SQL 語句在SGA 中的共享池中是否存在, 如果不存在,則編譯、解析後執行:硬解析,

     如果已存在,則取出編譯結果執行:軟解析)

事例執行過程:

DECLARE

 time1 NUMBER;

 time2 NUMBER;

BEGIN

  time1 := dbms_utility.get_time;

  EXECUTE IMMEDIATE 'truncate table t_demo';

  FOR i IN 1..10000

  LOOP

     EXECUTE IMMEDIATE

       'insert into t1(u_id) values(:x)' USING i; 

  END LOOP;

  time2 := dbms_utility.get_time;

  dbms_output.put_line((time2-time1)/100||'秒');

END;        

執行時間:pst+batch用時:10.55秒

 

3 利用forall進行批處理<相似java中的批處理>

使用範圍:當執行相似sql語句的次數比較多(例如使用者登陸,對錶頻繁操作..)FORALL只能執行一條sql語句。

優點:減少與SQL引擎轉替次數,提交效率。

缺點:迴圈時從開始到結束,一次性讀完,不能取其中的某些迴圈進行操作。

原理:使用ForAll語句可以讓PL/SQL引擎一次將多條SQL轉發給SQL引擎,從而減少轉發次數,提高系統效能。

事例執行過程:

CREATE  OR  REPLACE  PROCEDURE  p_test

AS

   --定義新的型別(不是定義變數!)

   TYPE id_table_type IS TABLE OF NUMBER(6) INDEX BY BINARY_INTEGER; 

   --用新的型別來定義變數

   id_table id_table_type;

   time1 NUMBER;

 time2 NUMBER;

BEGIN

    FOR i IN 1..10000

    LOOP

       --往兩個陣列中存放資料

       id_table(i) := i;  

    END LOOP;

   --★:一次性向SQL引擎傳遞了多條資料而不是一條

    time1 := dbms_utility.get_time;

    FORALL i IN 1..id_table.COUNT

        INSERT INTO t_demo(u_id) VALUES(id_table(i));

  time2 := dbms_utility.get_time;

  dbms_output.put_line((time2-time1)*10||'毫秒');

end p_test;

 

call p_test();

執行時間:pst+batch用時:170毫秒

 

4 利用BULK COLLECT

使用範圍:處理資料量比較少時。

優點:一次讀取多行資料,提高效率。

缺點: 需要較大的記憶體開銷

原理:將多個行引入一個或多個集合中。

事例執行過程:

DECLARE

   TYPE books_aat

 

      IS TABLE OF book%ROWTYPE

      INDEX BY PLS_INTEGER;

   book books_aat;

BEGIN

   SELECT *

     BULK COLLECT INTO book

     FROM books

    WHERE title LIKE '%PL/SQL%';

   ...

END;


注:本文整理自網路



相關文章