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

