PreparedStatement和Statement的區(qū)別

概念
Java提供了 Statement、PreparedStatement 和 CallableStatement三種方式來執(zhí)行查詢語句,其中 Statement 用于通用查詢, PreparedStatement 用于執(zhí)行參數(shù)化查詢,而 CallableStatement則是用于存儲(chǔ)過程。
PreparedStatement是java.sql包下面的一個(gè)接口,用來執(zhí)行SQL語句查詢,通過調(diào)用connection.preparedStatement(sql)方法可以獲得PreparedStatment對(duì)象。數(shù)據(jù)庫(kù)系統(tǒng)會(huì)對(duì)sql語句進(jìn)行預(yù)編譯處理(如果JDBC驅(qū)動(dòng)支持的話),預(yù)處理語句將被預(yù)先編譯好,這條預(yù)編譯的sql查詢語句能在將來的查詢中重用,這樣一來,它比Statement對(duì)象生成的查詢速度更快。下面是一個(gè)預(yù)處理語句的例子:

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

預(yù)處理語句的優(yōu)勢(shì)
1.PreparedStatement可以寫動(dòng)態(tài)參數(shù)化的查詢
用PreparedStatement你可以寫帶參數(shù)的sql查詢語句,通過使用相同的sql語句和不同的參數(shù)值來做查詢比創(chuàng)建一個(gè)不同的查詢語句要好,下面是一個(gè)參數(shù)化查詢:
SELECT interest_rate FROM loan WHERE loan_type=?
?表示占位符,在程序中可以使用不同的參數(shù)來替換占位符,實(shí)現(xiàn)參數(shù)化查詢。

  1. PreparedStatement比Statement 更快
    使用 PreparedStatement 最重要的一點(diǎn)好處是它擁有更佳的性能優(yōu)勢(shì),SQL語句會(huì)預(yù)編譯在數(shù)據(jù)庫(kù)系統(tǒng)中。執(zhí)行計(jì)劃同樣會(huì)被緩存起來,它允許數(shù)據(jù)庫(kù)做參數(shù)化查詢。使用預(yù)處理語句比普通的查詢更快,因?yàn)樗龅墓ぷ鞲伲〝?shù)據(jù)庫(kù)對(duì)SQL語句的分析,編譯,優(yōu)化已經(jīng)在第一次查詢前完成了)。為了減少數(shù)據(jù)庫(kù)的負(fù)載,生產(chǎn)環(huán)境中德JDBC代碼你應(yīng)該總是使用PreparedStatement 。值得注意的一點(diǎn)是:為了獲得性能上的優(yōu)勢(shì),應(yīng)該使用參數(shù)化sql查詢而不是字符串追加的方式。下面兩個(gè)SELECT 查詢,第一個(gè)SELECT查詢就沒有任何性能優(yōu)勢(shì)。
    SQL Query 1:字符串追加形式的PreparedStatement
    String loanType = getLoanType();
    PreparedStatement prestmt = conn.prepareStatement("select banks from loan where loan_type=" + loanType);
    SQL Query 2:使用參數(shù)化查詢的PreparedStatement
    PreparedStatement prestmt = conn.prepareStatement("select banks from loan where loan_type=?");
    prestmt.setString(1,loanType);

3.PreparedStatement可以防止SQL注入式攻擊
如果你是做Java web應(yīng)用開發(fā)的,那么必須熟悉那聲名狼藉的SQL注入式攻擊。去年Sony就遭受了SQL注入攻擊,被盜用了一些Sony play station(PS機(jī))用戶的數(shù)據(jù)。在SQL注入攻擊里,惡意用戶通過SQL元數(shù)據(jù)綁定輸入,比如:某個(gè)網(wǎng)站的登錄驗(yàn)證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';"
因?yàn)閃HERE條件恒為真,這就相當(dāng)于執(zhí)行:
strSQL = "SELECT * FROM users;"
因此可以達(dá)到無賬號(hào)密碼亦可登錄網(wǎng)站。如果惡意用戶要是更壞一點(diǎn),用戶填入:
strSQL = "SELECT * FROM users;"
SQL語句變成了:
strSQL = "SELECT * FROM users WHERE name = 'any_value' and pw = ''; DROP TABLE users"
這樣一來,雖然沒有登錄,但是數(shù)據(jù)表都被刪除了。

然而使用PreparedStatement的參數(shù)化的查詢可以阻止大部分的SQL注入。在使用參數(shù)化查詢的情況下,數(shù)據(jù)庫(kù)系統(tǒng)(eg:MySQL)不會(huì)將參數(shù)的內(nèi)容視為SQL指令的一部分來處理,而是在數(shù)據(jù)庫(kù)完成SQL指令的編譯后,才套用參數(shù)運(yùn)行,因此就算參數(shù)中含有破壞性的指令,也不會(huì)被數(shù)據(jù)庫(kù)所運(yùn)行。
補(bǔ)充:避免SQL注入的第二種方式:
在組合SQL字符串的時(shí)候,先對(duì)所傳入的參數(shù)做字符取代(將單引號(hào)字符取代為連續(xù)2個(gè)單引號(hào)字符,因?yàn)檫B續(xù)2個(gè)單引號(hào)字符在SQL數(shù)據(jù)庫(kù)中會(huì)視為字符中的一個(gè)單引號(hào)字符,譬如:
1
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'
這樣數(shù)據(jù)庫(kù)就會(huì)去系統(tǒng)查找name為“1′ ‘ OR 1=1”的記錄,而避免了SQL注入。

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

PreparedStatement的局限性
盡管PreparedStatement非常實(shí)用,但是它仍有一定的限制。

  1. 為了防止SQL注入攻擊,PreparedStatement不允許一個(gè)占位符(?)有多個(gè)值,在執(zhí)行有IN子句查詢的時(shí)候這個(gè)問題變得棘手起來。下面這個(gè)SQL查詢使用PreparedStatement就不會(huì)返回任何結(jié)果
    SELECT * FROM loan WHERE loan_type IN (?)
    preparedSatement.setString(1, "'personal loan', 'home loan', 'gold loan'");

關(guān)于PreparedStatement接口,需要重點(diǎn)記住的是:

  1. PreparedStatement可以寫參數(shù)化查詢,比Statement能獲得更好的性能。
  2. 對(duì)于PreparedStatement來說,數(shù)據(jù)庫(kù)可以使用已經(jīng)編譯過及定義好的執(zhí)行計(jì)劃,這種預(yù)處理語句查詢比普通的查詢運(yùn)行速度更快。
  3. PreparedStatement可以阻止常見的SQL注入式攻擊。
  4. PreparedStatement可以寫動(dòng)態(tài)查詢語句
  5. PreparedStatement與java.sql.Connection對(duì)象是關(guān)聯(lián)的,一旦你關(guān)閉了connection,PreparedStatement也沒法使用了。
  6. “?” 叫做占位符。
  7. PreparedStatement查詢默認(rèn)返回FORWARD_ONLY的ResultSet,你只能往一個(gè)方向移動(dòng)結(jié)果集的游標(biāo)。當(dāng)然你還可以設(shè)定為其他類型的值如:”CONCUR_READ_ONLY”。
  8. 不支持預(yù)編譯SQL查詢的JDBC驅(qū)動(dòng),在調(diào)用connection.prepareStatement(sql)的時(shí)候,它不會(huì)把SQL查詢語句發(fā)送給數(shù)據(jù)庫(kù)做預(yù)處理,而是等到執(zhí)行查詢動(dòng)作的時(shí)候(調(diào)用executeQuery()方法時(shí))才把查詢語句發(fā)送個(gè)數(shù)據(jù)庫(kù),這種情況和使用Statement是一樣的。
  9. 占位符的索引位置從1開始而不是0,如果填入0會(huì)導(dǎo)致java.sql.SQLException invalid column index異常。所以如果PreparedStatement有兩個(gè)占位符,那么第一個(gè)參數(shù)的索引時(shí)1,第二個(gè)參數(shù)的索引是2.

參考資料
原文鏈接:http://www.importnew.com/5006.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容