JDBC中PreparedStatement詳解以及應用場景實例介紹
前言
在Java中,當需要向數(shù)據(jù)庫中執(zhí)行SQL語句并傳遞參數(shù)時,我們通常會使用PreparedStatement接口。PreparedStatement繼承自Statement接口,用于預編譯SQL語句并執(zhí)行參數(shù)化查詢,這樣可以提高執(zhí)行效率并防止SQL注入攻擊。
1、PreparedStatement介紹
PreparedStatement是Java JDBC API的一部分,它提供了一種更有效率和安全的方式來向SQL語句傳遞參數(shù)。PreparedStatement允許我們執(zhí)行帶有動態(tài)參數(shù)的SQL語句,這些參數(shù)可以在執(zhí)行SQL語句之前預編譯,從而提高執(zhí)行效率。PreparedStatement對象可以通過Connection對象創(chuàng)建,并接受一條SQL語句作為參數(shù)。
PreparedStatement接口中定義了一系列用于設置參數(shù)的方法,包括setInt、setString、setDate等等,這些方法用于指定預編譯的SQL語句中的參數(shù)值。PreparedStatement還提供了一種用于執(zhí)行查詢的方法executeQuery(),以及用于執(zhí)行非查詢的方法executeUpdate()。
2、與Statement相比,PreparedStatement有以下優(yōu)點:
1.更高的執(zhí)行效率
當需要執(zhí)行多次相同的SQL語句時,PreparedStatement能夠將SQL語句預編譯,從而提高執(zhí)行效率。PreparedStatement對象在創(chuàng)建時,會將SQL語句編譯成一種可重用的二進制格式,當調用execute()方法時,直接傳遞參數(shù)即可執(zhí)行SQL語句,這比Statement每次都需要解析SQL語句要更加高效。
2.防止SQL注入攻擊
使用Statement執(zhí)行動態(tài)SQL語句時,需要將參數(shù)值拼接到SQL語句中,這樣容易受到SQL注入攻擊。而PreparedStatement通過參數(shù)化查詢的方式,將參數(shù)值作為參數(shù)傳遞給SQL語句,從而避免了SQL注入攻擊。
3.增加了代碼的可讀性
PreparedStatement的代碼更加簡潔明了,可以使代碼更易于理解和維護。
3、PreparedStatement的應用場景
PreparedStatement適用于執(zhí)行需要傳遞參數(shù)的SQL語句,特別是當需要執(zhí)行多次相同的SQL語句時,PreparedStatement能夠大大提高執(zhí)行效率。下面是PreparedStatement的一些常見應用場景:
通用的增、刪、改操作,可以直接調用此方法
public void update(String sql,Object ... args){ Connection conn = null; PreparedStatement ps = null; try { //1.獲取數(shù)據(jù)庫的連接 conn = JDBCUtils.getConnection(); //2.獲取PreparedStatement的實例 (或:預編譯sql語句) ps = conn.prepareStatement(sql); //3.填充占位符 for(int i = 0;i < args.length;i++){ ps.setObject(i + 1, args[i]); } //4.執(zhí)行sql語句 ps.execute(); } catch (Exception e) { e.printStackTrace(); }finally{ //5.關閉資源 JDBCUtils.closeResource(conn, ps); } }
1)增加表數(shù)據(jù):
String sql = "update students set grade = ? where id = ?"; update(sql,4,90);
2)更新表數(shù)據(jù):
String sql = "update students set grade = ? where id = ?"; update(sql,4,90);
3) 刪除表數(shù)據(jù):
String sql = "delete from students where id = ?"; update(sql,4);
4、使用PreparedStatement實現(xiàn)查詢操作
首先我們需要學習一種思想:
ORM思想(object relational mapping)
一個數(shù)據(jù)表對應一個java類
表中的一條記錄對應java類的一個對象
表中的一個字段對應java類的一個屬性
我們首先新建一個students類,內容如下:
public class Students { private int id; private String name; private int grade; public Students() { } public Students(int id, String name, int grade) { this.id = id; this.name = name; this.grade = grade; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getGrade() { return grade; } public void setGrade(int grade) { this.grade = grade; } @Override public String toString() { return id+","+name+","+grade; } }
1)單條數(shù)據(jù)的查詢
// 通用的針對于不同表的查詢:返回一個對象 (version 1.0) public <T> T getInstance(Class<T> clazz, String sql, Object... args) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { // 1.獲取數(shù)據(jù)庫連接 conn = JDBCUtils.getConnection(); // 2.預編譯sql語句,得到PreparedStatement對象 ps = conn.prepareStatement(sql); // 3.填充占位符 for (int i = 0; i < args.length; i++) { ps.setObject(i + 1, args[i]); } // 4.執(zhí)行executeQuery(),得到結果集:ResultSet rs = ps.executeQuery(); // 5.得到結果集的元數(shù)據(jù):ResultSetMetaData ResultSetMetaData rsmd = rs.getMetaData(); // 6.1通過ResultSetMetaData得到columnCount,columnLabel;通過ResultSet得到列值 int columnCount = rsmd.getColumnCount(); if (rs.next()) { T t = clazz.newInstance(); for (int i = 0; i < columnCount; i++) {// 遍歷每一個列 // 獲取列值 Object columnVal = rs.getObject(i + 1); // 獲取列的別名:列的別名,使用類的屬性名充當 String columnLabel = rsmd.getColumnLabel(i + 1); // 6.2使用反射,給對象的相應屬性賦值 Field field = clazz.getDeclaredField(columnLabel); field.setAccessible(true); field.set(t, columnVal); } return t; } } catch (Exception e) { e.printStackTrace(); } finally { // 7.關閉資源 JDBCUtils.closeResource(conn, ps, rs); } return null; }
調用上面的方法,實現(xiàn)查詢單條記錄:
String sql = "select * from students where id = ?"; Students s = getInstance(Students.class,sql,3); System.out.println(s);
2)多條數(shù)據(jù)的查詢
public <T> List<T> getForList(Class<T> clazz, String sql, Object... args){ Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { //1.獲取數(shù)據(jù)庫連接 conn = JDBCUtils.getConnection(); //2.預編譯sql語句,得到preparedStatement對象 ps = conn.prepareStatement(sql); //3.填充占位符 for (int i = 0; i < args.length; i++) { ps.setObject(i+1,args[i]); } //4.執(zhí)行executeQuery(),得到結果集:resultset rs = ps.executeQuery(); //5.獲取結果集的元數(shù)據(jù)ResultSetMetaData ResultSetMetaData rsmd = rs.getMetaData(); //6.通過獲取ResultSetMetaData結果集的列數(shù) int columnCount = rsmd.getColumnCount(); //7.創(chuàng)建集合對象 ArrayList<T> list = new ArrayList<T>(); while (rs.next()){ T t = clazz.newInstance(); //處理結果集一行數(shù)據(jù)中的每一個列 for (int i = 0; i < columnCount; i++) { Object value = rs.getObject(i+1); //獲取每個列的別名getColumnLabel---針對于表的字段名和類的屬性名不同 String columnName = rsmd.getColumnLabel(i+1); //給s對象指定的某個屬性,賦值為value Field field = clazz.getDeclaredField(columnName); field.setAccessible(true); field.set(t,value); } list.add(t); } return list; } catch (Exception e) { e.printStackTrace(); }finally { JDBCUtils.closeResource(conn,ps,rs); } return null; }
調用上面的方法,實現(xiàn)查詢多條記錄:
String sql = "select * from students"; List<Students> list = getForList(Students.class,sql); list.forEach(System.out::println);
PreparedStatement的注意事項
1.SQL注入攻擊
雖然PreparedStatement可以有效地防止SQL注入攻擊,但是在設置參數(shù)時也要注意一些細節(jié)。例如,在使用setString()方法設置字符串類型的參數(shù)時,應該使用單引號將字符串括起來。否則,可能會造成SQL語句語法錯誤。
PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM users WHERE username = ?"); pstmt.setString(1, "張三"); //正確方式 pstmt.setString(1, 張三); //錯誤方式
2.資源的釋放
與Statement一樣,使用完PreparedStatement對象后,我們也需要手動關閉對象以釋放資源,防止資源泄露。
關閉PreparedStatement對象可以調用PreparedStatement對象的close()方法。
下面是一個使用PreparedStatement的示例代碼:
public void queryUsersByName(String name) throws SQLException { Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; try { conn = getConnection(); String sql = "SELECT * FROM users WHERE name = ?"; pstmt = conn.prepareStatement(sql); pstmt.setString(1, name); rs = pstmt.executeQuery(); while (rs.next()) { // 處理結果集 } } finally { // 釋放資源 if (rs != null) { rs.close(); } if (pstmt != null) { pstmt.close(); } if (conn != null) { conn.close(); } } }
在finally塊中,我們按照ResultSet、PreparedStatement、Connection的順序關閉對象。使用try-finally塊可以確保即使在處理過程中出現(xiàn)異常,也能夠及時關閉對象以釋放資源。
3.批處理操作
批處理是指一次向數(shù)據(jù)庫發(fā)送多條 SQL 語句進行執(zhí)行的操作,可以有效提高數(shù)據(jù)庫操作效率。在使用 Statement 執(zhí)行批處理時,需要在 SQL 語句中使用分號(;)來分隔多個 SQL 語句,但是在使用 PreparedStatement 執(zhí)行批處理時,只需要在多次調用 addBatch() 方法時,傳入不同的 SQL 語句即可。
以下是一個 PreparedStatement 批處理的示例:
String sql = "INSERT INTO users(name, age) VALUES(?,?)"; PreparedStatement ps = conn.prepareStatement(sql); for(int i=0; i<10; i++){ ps.setString(1, "user"+i); ps.setInt(2, i); ps.addBatch(); } int[] result = ps.executeBatch();
4.數(shù)據(jù)庫事務
事務是指一組操作,要么全部執(zhí)行成功,要么全部執(zhí)行失敗。在數(shù)據(jù)庫操作中,事務通常用于保證數(shù)據(jù)的一致性和完整性,一旦某個操作失敗,整個事務將回滾到最初狀態(tài),所有操作都將失效。
在使用 PreparedStatement 進行數(shù)據(jù)庫操作時,可以結合事務來保證數(shù)據(jù)的一致性和完整性。在 JDBC 中,事務的使用可以通過 Connection 對象來實現(xiàn),其核心方法如下:
- setAutoCommit(boolean autoCommit): 設置是否自動提交事務,默認值為 true,即自動提交。
- commit(): 手動提交事務。
- rollback(): 回滾事務。
以下是一個 PreparedStatement 結合事務的示例:
Connection conn = null; PreparedStatement ps = null; try{ conn = getConnection(); // 關閉自動提交,開啟事務 conn.setAutoCommit(false); String sql = "INSERT INTO users(name, age) VALUES(?,?)"; ps = conn.prepareStatement(sql); ps.setString(1, "user1"); ps.setInt(2, 20); ps.executeUpdate(); ps.setString(1, "user2"); ps.setInt(2, 30); ps.executeUpdate(); // 手動提交事務 conn.commit(); }catch(SQLException e){ // 回滾事務 if(conn != null){ try{ conn.rollback(); }catch(SQLException ex){ ex.printStackTrace(); } } e.printStackTrace(); }finally{ JdbcUtils.release(conn, ps, null); }
總結
PreparedStatement 是一種高效、安全、易用的數(shù)據(jù)庫操作方式,具有多種優(yōu)點,如可防止 SQL 注入攻擊、支持參數(shù)化查詢、可以重復使用等。在實際開發(fā)中,建議優(yōu)先選擇使用 PreparedStatement 進行數(shù)據(jù)庫操作。
如果還有其他問題或需要進一步了解 PreparedStatement,可以查閱 JDK API 文檔或者其他相關資料。
相對于Statement,PreparedStatement的優(yōu)點是什么?
1、PreparedStatement有助于防止SQL注入,因為它會自動對特殊字符轉義。
2、PreparedStatement可以用來進行動態(tài)查詢。
3、PreparedStatement執(zhí)行更快。尤其當你重用它或者使用它的拼量查詢接口執(zhí)行多條語句時。
4、使用PreparedStatement的setter方法更容易寫出面向對象的代碼,而Statement的話,我們得拼接字符串來生成查詢語句。
如果參數(shù)太多了,字符串拼接看起來會非常丑陋并且容易出錯。
到此這篇關于JDBC中PreparedStatement詳解以及應用場景實例介紹的文章就介紹到這了,更多相關JDBC中PreparedStatement詳解內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java中的FutureTask實現(xiàn)異步任務代碼實例
這篇文章主要介紹了Java中的FutureTask實現(xiàn)異步任務代碼實例,普通的線程執(zhí)行是無法獲取到執(zhí)行結果的,FutureTask?間接實現(xiàn)了?Runnable?和?Future?接口,可以得到子線程耗時操作的執(zhí)行結果,AsyncTask?異步任務就是使用了該機制,需要的朋友可以參考下2024-01-01Spring Boot的filter(過濾器)簡單使用實例詳解
過濾器(Filter)的注冊方法和 Servlet 一樣,有兩種方式:代碼注冊或者注解注冊,下面通過實例給大家介紹Spring Boot的filter(過濾器)簡單使用,一起看看吧2017-04-04SpringCloud zookeeper作為注冊中心使用介紹
ZooKeeper由雅虎研究院開發(fā),是Google Chubby的開源實現(xiàn),后來托管到Apache,于2010年11月正式成為Apache的頂級項目。ZooKeeper是一個經(jīng)典的分布式數(shù)據(jù)一致性解決方案,致力于為分布式應用提供一個高性能、高可用,且具有嚴格順序訪問控制能力的分布式協(xié)調服務2022-11-11