為什麼要使用PreparedStatement?
一、通過PreparedStatement提升性能
Statement主要用於執行靜態SQL語句,即內容固定不變的SQL語句。Statement每執行一次都要對傳入的SQL語句編譯一次,效率較差。
某些情況下,SQL語句只是其中的參數有所不同,其余子句完全相同,適用於PreparedStatement。
PreparedStatement的另外一個好處就是預防sql注入攻擊
PreparedStatement是接口,繼承自Statement接口。
使用PreparedStatement時,SQL語句已提前編譯,三種常用方法 execute、 executeQuery 和 executeUpdate 已被更改,以使之不再需要參數。
PreparedStatement 實例包含已事先編譯的 SQL 語句,SQL 語句可有一個或多個 IN 參數,IN參數的值在 SQL 語句創建時未被指定。該語句為每個 IN 參數保留一個問號(“?”)作為占位符。
每個問號的值必須在該語句執行之前,通過適當的setInt或者setString 等方法提供。
由於 PreparedStatement 對象已預編譯過,所以其執行速度要快於 Statement 對象。因此,多次執行的 SQL 語句經常創建為 PreparedStatement 對象,以提高效率。
通常批量處理時使用PreparedStatement。
1 //SQL語句已發送給數據庫,並編譯好為執行作好准備 2 PreparedStatement pstmt = con.prepareStatement( 3 "UPDATE emp SET job= ? WHERE empno = ?"); 4 //對占位符進行初始化 5 pstmt.setLong(1, "Manager"); 6 pstmt.setInt(2,1001); 7 //執行SQL語句 8 pstmt.executeUpdate();
小案例:分別向數據庫插入1000條記錄。分別記錄執行時間,然後進行比較。
新建項目:

使用Statement的 執行效率的類INSERT3:
1 package com.cnblogs.daliu_it;
2
3 import java.sql.Connection;
4 import java.sql.PreparedStatement;
5 import java.sql.Statement;
6
7 /**
8 * 使用Statement的 執行效率
9 *
10 */
11 public class INSERT3 {
12
13 public static void main(String[] args) {
14 Connection conn = null;
15 try {
16 conn = DBUtility.getConnection();
17 Statement state = conn.createStatement();
18 long start = System.currentTimeMillis();
19
20 for (int i = 7000; i < 8000; i++) {
21 String sql = "INSERT INTO user VALUES" + "(" + i + ","
22 + "'test" + i + "'," + "'12345'," + "5000," + "'test"
23 + i + "@qq.com'" + ")";
24 state.executeUpdate(sql);
25 }
26 System.out.println("插入完畢");
27 long end = System.currentTimeMillis();
28 System.out.println("耗時:" + (end - start));
29 } catch (Exception e) {
30 System.out.println("插入數據失敗");
31 e.printStackTrace();
32 } finally {
33 DBUtility.closeConnection(conn);
34 }
35 }
36 }
測試數據:

使用預編譯PreparedStatement SQL提高執行效率的類INSERT2:
1 package com.cnblogs.daliu_it;
2
3 import java.sql.Connection;
4 import java.sql.PreparedStatement;
5
6 /**
7 * 使用預編譯PreparedStatement SQL提高執行效率
8 *
9 */
10 public class INSERT2 {
11 public static void main(String[] args) {
12 Connection conn = null;
13 try {
14 conn = DBUtility.getConnection();
15 // Statement state= conn.createStatement();
16
17 String sql = "INSERT INTO user VALUES(?,?,'123456',?,?)";
18 /*
19 * 根據給定的預編譯SQL語句創建一個 PreparedStatement
20 */
21 PreparedStatement ps = conn.prepareStatement(sql);
22
23 long start = System.currentTimeMillis();
24
25 for (int i = 9000; i < 10000; i++) {
26 ps.setInt(1, i);
27 ps.setString(2, "test" + i);
28 ps.setInt(3, 5000);
29 ps.setString(4, "test" + i + "@qq.com");
30 ps.executeUpdate();
31 }
32 System.out.println("插入完畢");
33 long end = System.currentTimeMillis();
34 System.out.println("耗時:" + (end - start));
35 } catch (Exception e) {
36 System.out.println("插入數據失敗");
37 e.printStackTrace();
38 } finally {
39 DBUtility.closeConnection(conn);
40 }
41 }
42 }
測試效果:

二、通過PreparedStatement防止SQL Injection
對JDBC而言,SQL注入攻擊只對Statement有效,對PreparedStatement無效,因為PreparedStatement不允許在插入參數時改變SQL語句的邏輯結構。
使用預編譯的語句對象時,用戶傳入的任何數據不會和原SQL語句發生匹配關系,無需對輸入的數據做過濾。如果用戶將”or 1 = 1”傳入賦值給占位符,下述SQL語句將無法執行:select * from t where username = ? and password = ?;
PreparedStatement是Statement的子類,表示預編譯的SQL語句的對象。在使用PreparedStatement對象執行SQL命令時,命令被數據庫編譯和解析,並放入命令緩沖區。緩沖區中的預編譯SQL命令可以重復使用。
1 sql = "select * from users where NAME = ? and PWD = ?"; 2 System.out.println(sql); 3 4 5 con = DBUtility.getConnection(); 6 7 //通過Statement 的改為prepareStatement 8 stmt = con.prepareStatement(sql); 9 10 11 // rs = stmt.executeQuery(sql); 12 13 stmt.setString(1, username); 14 stmt.setString(2, password); 15 rs = stmt.executeQuery();
使用PreparedStatement來執行SQL語句。在SQL語句中有2個問號,在代碼中要給它們分別設置值,規則是:從左到右,對應1,2,...。
對於JDBC而言,SQL注入攻擊只對Statement有效,對PreparedStatement是無效的,這是因為PreparedStatement不允許在插入時改變查詢的邏輯結構。
例子:使用PreparedStatement實現用戶名和密碼的驗證功能。