JDBC爲何要使用PreparedStatement而不是Statement

1、前言

PreparedStatement是用來執行SQL查詢語句的API之一php

Java提供了 StatementPreparedStatement 和 CallableStatement三種方式來執行查詢語句html

其中 :java

  •  Statement                  用於通用查詢mysql

  •  PreparedStatement   用於執行參數化查詢web

  •  CallableStatement      用於存儲過程。面試

同時PreparedStatement還常常會在Java面試被說起,譬如:Statement與PreparedStatement的區別以及如何避免SQL注入式攻擊?這篇教程中咱們會討論爲何要用PreparedStatement?使用PreparedStatement有什麼樣的優點?PreparedStatement又是如何避免SQL注入攻擊的?sql

2、PreparedStatement是什麼?

PreparedStatement是java.sql包下面的一個接口,用來執行SQL語句查詢,經過調用connection.preparedStatement(sql)方法能夠得到PreparedStatment對象。數據庫

數據庫系統會對sql語句進行預編譯處理(若是JDBC驅動支持的話),預處理語句將被預先編譯好,這條預編譯的sql查詢語句能在未來的查詢中重用,這樣一來,它比Statement對象生成的查詢速度更快。緩存

下面是一個例子:安全

public class PreparedStmtExample {
  
    public static void main(String args[]) throws SQLException {
        Connection conn = DriverManager.getConnection("mysql:\\localhost:1520", "root", "root");
        PreparedStatement preStatement = conn.prepareStatement("select distinct loan_type from loan where bank=?");
        preStatement.setString(1, "Citibank");
  
        ResultSet result = preStatement.executeQuery();
  
        while(result.next()){
            System.out.println("Loan Type: " + result.getString("loan_type"));
        }       
    }
} 
Output:
Loan Type: Personal Loan
Loan Type: Auto Loan
Loan Type: Home Loan
Loan Type: Gold Loan

 

這個例子中,若是仍是用 PreparedStatement 作一樣的查詢,哪怕參數值不同,好比:」Standard Chated」 或者」HSBC」做爲參數值,數據庫系統仍是會去調用以前編譯器編譯好的執行語句(系統庫系統初次會對查詢語句作最大的性能優化)。

默認會返回」TYPE_FORWARD_ONLY」類型的結果集( ResultSet ),固然你也可使用preparedstatment()的重載方法返回不一樣類型的結果集。

3、預處理語句的優點

PreparedStatement提供了諸多好處,企業級應用開發中強烈推薦使用PreparedStatement來作SQL查詢,下面列出PreparedStatement的幾點優點。

1)PreparedStatement能夠寫動態參數化的查詢

用PreparedStatement你能夠寫帶參數的sql查詢語句,經過使用相同的sql語句和不一樣的參數值來作查詢比建立一個不一樣的查詢語句要好,下面是一個參數化查詢: 

SELECT interest_rate FROM loan WHERE loan_type=?

如今你可使用任何一種loan類型如:」personal loan」,」home loan」 或者」gold loan」來查詢,這個例子叫作參數化查詢,由於它能夠用不一樣的參數調用它,這裏的」?」就是參數的佔位符。 

2)PreparedStatement比 Statement 更快

使用 PreparedStatement 最重要的一點好處是它擁有更佳的性能優點,SQL語句會預編譯在數據庫系統中。執行計劃一樣會被緩存起來,它容許數據庫作參數化查詢。使用預處理語句比普通的查詢更快,由於它作的工做更少(數據庫對SQL語句的分析,編譯,優化已經在第一次查詢前完成了)。爲了減小數據庫的負載,生產環境中德JDBC代碼你應該老是使用PreparedStatement 。值得注意的一點是:爲了得到性能上的優點,應該使用參數化sql查詢而不是字符串追加的方式。下面兩個SELECT 查詢,第一個SELECT查詢就沒有任何性能優點。

SQL Query 1:字符串追加形式的PreparedStatemen

String loanType = getLoanType();
PreparedStatement prestmt = conn.prepareStatement("select banks from loan where loan_type=" + loanType);

SQL Query 2:使用參數化查詢的PreparedStatement

PreparedStatement prestmt = conn.prepareStatement("select banks from loan where loan_type=?");
prestmt.setString(1,loanType);

第二個查詢就是正確使用PreparedStatement的查詢,它比SQL1能得到更好的性能。 

3)PreparedStatement能夠防止SQL注入式攻擊

若是你是作Java web應用開發的,那麼必須熟悉那聲名狼藉的SQL注入式攻擊。去年Sony就遭受了SQL注入攻擊,被盜用了一些Sony play station(PS機)用戶的數據。在SQL注入攻擊裏,惡意用戶經過SQL元數據綁定輸入,好比:某個網站的登陸驗證SQL查詢代碼爲: 

strSQL = "SELECT * FROM users WHERE name = '" + userName + "' and pw = '"+ passWord +"';"

惡意填入:

userName = "1' OR '1'='1";
passWord = "1' OR '1'='1";

那麼最終SQL語句變成了:

strSQL = "SELECT * FROM users WHERE name = '1' OR '1'='1' and pw = '1' OR '1'='1';"

由於WHERE條件恆爲真,這就至關於執行:

strSQL = "SELECT * FROM users;"

所以能夠達到無帳號密碼亦可登陸網站。若是惡意用戶要是更壞一點,用戶填入:

passWord = "1' OR '1'='1";DROP TABLE USERS;

SQL語句變成了:

strSQL = "SELECT * FROM users WHERE name = 'any_value' and pw = ''; DROP TABLE users"

這樣一來,雖然沒有登陸,可是數據表都被刪除了。 

然而使用PreparedStatement的參數化的查詢能夠阻止大部分的SQL注入。在使用參數化查詢的狀況下,數據庫系統(eg:MySQL)不會將參數的內容視爲SQL指令的一部分來處理,而是在數據庫完成SQL指令的編譯後,才套用參數運行,所以就算參數中含有破壞性的指令,也不會被數據庫所運行。

補充:避免SQL注入的第二種方式:
在組合SQL字符串的時候,先對所傳入的參數作字符取代(將單引號字符取代爲連續2個單引號字符,由於連續2個單引號字符在SQL數據庫中會視爲字符中的一個單引號字符,譬如:

strSQL = "SELECT * FROM users WHERE name = '" + userName + "';"

傳入字符串:

userName  = " 1' OR 1=1 "

把userName作字符替換後變成:

 userName = " 1'' OR 1=1"

最後生成的SQL查詢語句爲:

 strSQL = "SELECT * FROM users WHERE name = '1'' OR 1=1'

比起凌亂的字符串追加似的查詢,PreparedStatement查詢可讀性更好、更安全。 

4、PreparedStatement的侷限性


儘管PreparedStatement很是實用,可是它仍有必定的限制。 
1. 爲了防止SQL注入攻擊,PreparedStatement不容許一個佔位符(?)有多個值,在執行有**IN**子句查詢的時候這個問題變得棘手起來。下面這個SQL查詢使用PreparedStatement就不會返回任何結果 

5、不算總結的總結

關於PreparedStatement接口,須要重點記住的是:
    1. PreparedStatement能夠寫參數化查詢,比Statement能得到更好的性能。
    2. 對於PreparedStatement來講,數據庫可使用已經編譯過及定義好的執行計劃,這種預處理語句查詢比普通的查詢運行速度更快。
    3. PreparedStatement能夠阻止常見的SQL注入式攻擊。
    4. PreparedStatement能夠寫動態查詢語句
    5. PreparedStatement與java.sql.Connection對象是關聯的,一旦你關閉了connection,PreparedStatement也無法使用了。
    6. 「?」 叫作佔位符。
    7. PreparedStatement查詢默認返回FORWARD_ONLY的ResultSet,你只能往一個方向移動結果集的遊標。固然你還能夠設定爲其餘類型的值如:」CONCUR_READ_ONLY」。
    8. 不支持預編譯SQL查詢的JDBC驅動,在調用connection.prepareStatement(sql)的時候,它不會把SQL查詢語句發送給數據庫作預處理,而是等到執行查詢動做的時候(調用executeQuery()方法時)才把查詢語句發送個數據庫,這種狀況和使用Statement是同樣的。
    9. 佔位符的索引位置從1開始而不是0,若是填入0會致使*java.sql.SQLException invalid column index*異常。因此若是PreparedStatement有兩個佔位符,那麼第一個參數的索引時1,第二個參數的索引是2.

以上就是爲何要使用PreparedStatement的所有理由,不過你仍然可使用Statement對象用來作作測試。可是在生產環境下你必定要考慮使用 PreparedStatement 。

更多參考: 
SQL注入攻擊 
參數化查詢 
預處理語句與存儲過程

原文連接: Javarevisited 翻譯: ImportNew.com 劉志軍
譯文連接: http://www.importnew.com/5006.html

相關文章
相關標籤/搜索