為什麼要使用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實現用戶名和密碼的驗證功能。