Java Statement PK PrepareStatement
PreparedStatement是用來執(zhí)行SQL查詢語句的API之一,Java提供了?Statement、PreparedStatement?和?CallableStatement三種方式來執(zhí)行查詢語句,其中?Statement?用于通用查詢,?PreparedStatement?用于執(zhí)行參數(shù)化查詢,而?CallableStatement則是用于存儲(chǔ)過程。同時(shí)PreparedStatement還經(jīng)常會(huì)在Java面試被提及,譬如:Statement與PreparedStatement的區(qū)別以及如何避免SQL注入式攻擊?這篇教程中我們會(huì)討論為什么要用PreparedStatement?使用PreparedStatement有什么樣的優(yōu)勢?PreparedStatement又是如何避免SQL注入攻擊的?
PreparedStatement是什么?
PreparedStatement是java.sql包下面的一個(gè)接口,用來執(zhí)行SQL語句查詢,通過調(diào)用connection.preparedStatement(sql)方法可以獲得PreparedStatment對象。數(shù)據(jù)庫系統(tǒng)會(huì)對sql語句進(jìn)行預(yù)編譯處理(如果JDBC驅(qū)動(dòng)支持的話),預(yù)處理語句將被預(yù)先編譯好,這條預(yù)編譯的sql查詢語句能在將來的查詢中重用,這樣一來,它比Statement對象生成的查詢速度更快。下面是一個(gè)例子:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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 |
這個(gè)例子中,如果還是用?PreparedStatement?做同樣的查詢,哪怕參數(shù)值不一樣,比如:”Standard Chated” 或者”HSBC”作為參數(shù)值,數(shù)據(jù)庫系統(tǒng)還是會(huì)去調(diào)用之前編譯器編譯好的執(zhí)行語句(系統(tǒng)庫系統(tǒng)初次會(huì)對查詢語句做最大的性能優(yōu)化)。默認(rèn)會(huì)返回”TYPE_FORWARD_ONLY”類型的結(jié)果集(?ResultSet?),當(dāng)然你也可以使用preparedstatment()的重載方法返回不同類型的結(jié)果集。
預(yù)處理語句的優(yōu)勢
PreparedStatement提供了諸多好處,企業(yè)級(jí)應(yīng)用開發(fā)中強(qiáng)烈推薦使用PreparedStatement來做SQL查詢,下面列出PreparedStatement的幾點(diǎn)優(yōu)勢。
用PreparedStatement你可以寫帶參數(shù)的sql查詢語句,通過使用相同的sql語句和不同的參數(shù)值來做查詢比創(chuàng)建一個(gè)不同的查詢語句要好,下面是一個(gè)參數(shù)化查詢:
?
| 1 | SELECT interest_rate FROM loan WHERE loan_type=? |
現(xiàn)在你可以使用任何一種loan類型如:”personal loan”,”home loan” 或者”gold loan”來查詢,這個(gè)例子叫做參數(shù)化查詢,因?yàn)樗梢杂貌煌膮?shù)調(diào)用它,這里的”?”就是參數(shù)的占位符。
使用?PreparedStatement?最重要的一點(diǎn)好處是它擁有更佳的性能優(yōu)勢,SQL語句會(huì)預(yù)編譯在數(shù)據(jù)庫系統(tǒng)中。執(zhí)行計(jì)劃同樣會(huì)被緩存起來,它允許數(shù)據(jù)庫做參數(shù)化查詢。使用預(yù)處理語句比普通的查詢更快,因?yàn)樗龅墓ぷ鞲?#xff08;數(shù)據(jù)庫對SQL語句的分析,編譯,優(yōu)化已經(jīng)在第一次查詢前完成了)。為了減少數(shù)據(jù)庫的負(fù)載,生產(chǎn)環(huán)境中德JDBC代碼你應(yīng)該總是使用PreparedStatement?。值得注意的一點(diǎn)是:為了獲得性能上的優(yōu)勢,應(yīng)該使用參數(shù)化sql查詢而不是字符串追加的方式。下面兩個(gè)SELECT 查詢,第一個(gè)SELECT查詢就沒有任何性能優(yōu)勢。
SQL Query 1:字符串追加形式的PreparedStatement
?
| 1 2 | String loanType = getLoanType(); PreparedStatement prestmt = conn.prepareStatement("select banks from loan where loan_type=" + loanType); |
SQL Query 2:使用參數(shù)化查詢的PreparedStatement
| 1 2 | PreparedStatement prestmt = conn.prepareStatement("select banks from loan where loan_type=?"); prestmt.setString(1,loanType); |
第二個(gè)查詢就是正確使用PreparedStatement的查詢,它比SQL1能獲得更好的性能。
如果你是做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查詢代碼為:
?
| 1 | strSQL = "SELECT * FROM users WHERE name = '" + userName + "' and pw = '"+ passWord +"';" |
惡意填入:
| 1 2 | userName = "1' OR '1'='1"; passWord = "1' OR '1'='1"; |
那么最終SQL語句變成了:
| 1 | strSQL = "SELECT * FROM users WHERE name = '1' OR '1'='1' and pw = '1' OR '1'='1';" |
因?yàn)閃HERE條件恒為真,這就相當(dāng)于執(zhí)行:
| 1 | strSQL = "SELECT * FROM users;" |
因此可以達(dá)到無賬號(hào)密碼亦可登錄網(wǎng)站。如果惡意用戶要是更壞一點(diǎn),用戶填入:
| 1 | strSQL = "SELECT * FROM users;" |
SQL語句變成了:
| 1 | strSQL = "SELECT * FROM users WHERE name = 'any_value' and pw = ''; DROP TABLE users" |
這樣一來,雖然沒有登錄,但是數(shù)據(jù)表都被刪除了。
然而使用PreparedStatement的參數(shù)化的查詢可以阻止大部分的SQL注入。在使用參數(shù)化查詢的情況下,數(shù)據(jù)庫系統(tǒng)(eg:MySQL)不會(huì)將參數(shù)的內(nèi)容視為SQL指令的一部分來處理,而是在數(shù)據(jù)庫完成SQL指令的編譯后,才套用參數(shù)運(yùn)行,因此就算參數(shù)中含有破壞性的指令,也不會(huì)被數(shù)據(jù)庫所運(yùn)行。
補(bǔ)充:避免SQL注入的第二種方式:
在組合SQL字符串的時(shí)候,先對所傳入的參數(shù)做字符取代(將單引號(hào)字符取代為連續(xù)2個(gè)單引號(hào)字符,因?yàn)檫B續(xù)2個(gè)單引號(hào)字符在SQL數(shù)據(jù)庫中會(huì)視為字符中的一個(gè)單引號(hào)字符,譬如:
| 1 | strSQL = "SELECT * FROM users WHERE name = '" + userName + "';" |
傳入字符串:
| 1 | userName? = " 1' OR 1=1 " |
把userName做字符替換后變成:
| 1 | userName = " 1'' OR 1=1" |
最后生成的SQL查詢語句為:
| 1 | strSQL = "SELECT * FROM users WHERE name = '1'' OR 1=1' |
這樣數(shù)據(jù)庫就會(huì)去系統(tǒng)查找name為“1′ ‘ OR 1=1”的記錄,而避免了SQL注入。
PreparedStatement的局限性
盡管PreparedStatement非常實(shí)用,但是它仍有一定的限制。
1. 為了防止SQL注入攻擊,PreparedStatement不允許一個(gè)占位符(?)有多個(gè)值,在執(zhí)行有**IN**子句查詢的時(shí)候這個(gè)問題變得棘手起來。下面這個(gè)SQL查詢使用PreparedStatement就不會(huì)返回任何結(jié)果
| 1 2 | SELECT * FROM loan WHERE loan_type IN (?) preparedSatement.setString(1, "'personal loan', 'home loan', 'gold loan'"); |
那如何解決這個(gè)問題呢?請你繼續(xù)關(guān)注本博客,下期告訴你答案。
不算總結(jié)的總結(jié)
關(guān)于PreparedStatement接口,需要重點(diǎn)記住的是:
1. PreparedStatement可以寫參數(shù)化查詢,比Statement能獲得更好的性能。
2. 對于PreparedStatement來說,數(shù)據(jù)庫可以使用已經(jīng)編譯過及定義好的執(zhí)行計(jì)劃,這種預(yù)處理語句查詢比普通的查詢運(yùn)行速度更快。
3. PreparedStatement可以阻止常見的SQL注入式攻擊。
4. PreparedStatement可以寫動(dòng)態(tài)查詢語句
5. PreparedStatement與java.sql.Connection對象是關(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ù)庫做預(yù)處理,而是等到執(zhí)行查詢動(dòng)作的時(shí)候(調(diào)用executeQuery()方法時(shí))才把查詢語句發(fā)送個(gè)數(shù)據(jù)庫,這種情況和使用Statement是一樣的。
9. 占位符的索引位置從1開始而不是0,如果填入0會(huì)導(dǎo)致*java.sql.SQLException invalid column index*異常。所以如果PreparedStatement有兩個(gè)占位符,那么第一個(gè)參數(shù)的索引時(shí)1,第二個(gè)參數(shù)的索引是2.
以上就是為什么要使用PreparedStatement的全部理由,不過你仍然可以使用Statement對象用來做做測試。但是在生產(chǎn)環(huán)境下你一定要考慮使用?PreparedStatement?
轉(zhuǎn)載于:https://www.cnblogs.com/wusirAaron/p/10125756.html
總結(jié)
以上是生活随笔為你收集整理的Java Statement PK PrepareStatement的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 成都欢乐谷未成年可以买学生票吗
- 下一篇: Java Web 学习与总结(一)Ser