JDBC 中preparedStatement和Statement區別


一、概念
PreparedStatement是用來執行SQL查詢語句的API之一,Java提供了 Statement、PreparedStatement 和 CallableStatement三種方式來執行查詢語句,其中 Statement 用於通用查詢, PreparedStatement 用於執行參數化查詢,而 CallableStatement則是用於存儲過程。同時PreparedStatement還經常會在Java面試被提及,譬如:Statement與PreparedStatement的區別以及如何避免SQL注入式攻擊?這篇教程中我們會討論為什么要用PreparedStatement?使用PreparedStatement有什么樣的優勢?PreparedStatement又是如何避免SQL注入攻擊的?

1.PreparedStatement:
PreparedStatement是java.sql包下面的一個接口,用來執行SQL語句查詢,通過調用connection.preparedStatement(sql)方法可以獲得PreparedStatment對象。數據庫系統會對sql語句進行預編譯處理(如果JDBC驅動支持的話),預處理語句將被預先編譯好,這條預編譯的sql查詢語句能在將來的查詢中重用,這樣一來,它比Statement對象生成的查詢速度更快。

2.Statement
使用 Statement 對象。在對數據庫只執行一次性存取的時侯,用 Statement 對象進行處理。PreparedStatement 對象的開銷比Statement大,對於一次性操作並不會帶來額外的好處。

二、深入理解statement 和prepareStatement
1、使用Statement而不是PreparedStatement對象
JDBC驅動的最佳化是基於使用的是什么功能. 選擇PreparedStatement還是Statement取決於你要怎么使用它們. 對於只執行一次的SQL語句選擇Statement是最好的. 相反, 如果SQL語句被多次執行選用PreparedStatement是最好的.

PreparedStatement的第一次執行消耗是很高的. 它的性能體現在后面的重復執行. 例如, 假設我使用Employee ID, 使用prepared的方式來執行一個針對Employee表的查詢. JDBC驅動會發送一個網絡請求到數據解析和優化這個查詢. 而執行時會產生另一個網絡請求.在JDBC驅動中,減少網絡通訊是最終的目的. 如果我的程序在運行期間只需要一次請求, 那么就使用Statement. 對於Statement, 同一個查詢只會產生一次網絡到數據庫的通訊.

對於使用PreparedStatement池的情況下, 本指導原則有點復雜. 當使用PreparedStatement池時, 如果一個查詢很特殊, 並且不太會再次執行到, 那么可以使用Statement. 如果一個查詢很少會被執行,但連接池中的Statement池可能被再次執行, 那么請使用PreparedStatement. 在不是Statement池的同樣情況下, 請使用Statement.

2、使用PreparedStatement的Batch功能
Update大量的數據時, 先Prepare一個INSERT語句再多次的執行, 會導致很多次的網絡連接. 要減少JDBC的調用次數改善性能, 你可以使用PreparedStatement的AddBatch()方法一次性發送多個查詢給數據庫. 例如, 讓我們來比較一下下面的例子.

為了區分 “Statement、PreparedStatement、PreparedStatement + 批處理” 這三者之間的效率,下面的示例執行過程都是在數據庫表t1中插入1萬條記錄,並記錄出所需的時間(此時間與電腦硬件有關)。實驗結果如下:
1.使用Statement對象 用時31秒
2.預編譯PreparedStatement 用時14秒
3.使用PreparedStatement + 批處理 用時485毫秒

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只編譯一次,減少編譯次數
事例執行過程:
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();
            }//end of if
       }//end of for
       pst.executeBatch();
       Long endTime = System.currentTimeMillis();
       System.out.println("pst+batch用時:"+(endTime-beginTime)+"毫秒");
       pst.close();
       conn.close();
  } catch (SQLException e) {
        e.printStackTrace();
  }

}
執行時間:pst+batch用時:485毫秒
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
代碼轉自:http://blog.itpub.net/90618/viewspace-607949/

三、區別
1.代碼的可讀性和可維護性.
雖然用PreparedStatement來代替Statement會使代碼多出幾行,但這樣的代碼無論從可讀性還是可維護性上來說.都比直接用Statement的代碼高很多檔次:
stmt.executeUpdate(“insert into tb_name (col1,col2,col2,col4) values (‘”+var1+”’,’”+var2+”’,”+var3+”,’”+var4+”’)”);//stmt是Statement對象實例

perstmt = con.prepareStatement(“insert into tb_name (col1,col2,col2,col4) values (?,?,?,?)”);
perstmt.setString(1,var1);
perstmt.setString(2,var2);
perstmt.setString(3,var3);
perstmt.setString(4,var4);
perstmt.executeUpdate(); //prestmt是 PreparedStatement 對象實例

不用我多說,對於第一種方法.別說其他人去讀你的代碼,就是你自己過一段時間再去讀,都會覺得傷心.

2.PreparedStatement盡最大可能提高性能.
語句在被DB的編譯器編譯后的執行代碼被緩存下來,那么下次調用時只要是相同的預編譯語句就不需要編譯,只要將參數直接傳入編譯過的語句執行代碼中(相當於一個涵數)就會得到執行.這並不是說只有一個Connection中多次執行的預編譯語句被緩存,而是對於整個DB中,只要預編譯的語句語法和緩存中匹配.那么在任何時候就可以不需要再次編譯而可以直接執行.而statement的語句中,即使是相同一操作,而由於每次操作的數據不同所以使整個語句相匹配的機會極小,幾乎不太可能匹配.比如:
insert into tb_name (col1,col2) values (‘11’,’22’);
insert into tb_name (col1,col2) values (‘11’,’23’);
即使是相同操作但因為數據內容不一樣,所以整個個語句本身不能匹配,沒有緩存語句的意義.事實是沒有數據庫會對普通語句編譯后的執行代碼緩存.

當然並不是所以預編譯語句都一定會被緩存,數據庫本身會用一種策略,比如使用頻度等因素來決定什么時候不再緩存已有的預編譯結果.以保存有更多的空間存儲新的預編譯語句.

2.最重要的一點是極大地提高了安全性.

即使到目前為止,仍有一些人連基本的惡義SQL語法都不知道.
String sql = “select * from tb_name where name= ‘”+varname+”’ and passwd=’”+varpasswd+”’”;
如果我們把[’ or ‘1’ = ‘1]作為varpasswd傳入進來.用戶名隨意,看看會成為什么?

select * from tb_name = ‘隨意’ and passwd = ” or ‘1’ = ‘1’;
因為’1’=’1’肯定成立,所以可以任何通過驗證.更有甚者:
把[‘;drop table tb_name;]作為varpasswd傳入進來,則:
select * from tb_name = ‘隨意’ and passwd = ”;drop table tb_name;有些數據庫是不會讓你成功的,但也有很多數據庫就可以使這些語句得到執行.

而如果你使用預編譯語句.你傳入的任何內容就不會和原來的語句發生任何匹配的關系.只要全使用預編譯語句,你就用不着對傳入的數據做任何過慮.而如果使用普通的statement,有可能要對drop,;等做費盡心機的判斷和過慮.

四、總結
關於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 。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM