# Java中PreparedStatement的用法是什么
## 1. 概述
### 1.1 什么是PreparedStatement
PreparedStatement是Java JDBC API中的一個重要接口,它繼承自Statement接口,用于執行預編譯的SQL語句。與普通的Statement不同,PreparedStatement允許SQL語句包含參數占位符(通常用"?"表示),這些參數可以在執行前被動態設置。
### 1.2 與Statement的區別
| 特性 | Statement | PreparedStatement |
|---------------------|-------------------------------|--------------------------------|
| SQL注入風險 | 高 | 低 |
| 性能 | 每次執行都需編譯 | 預編譯,多次執行效率高 |
| 參數綁定 | 字符串拼接 | 使用setXxx()方法 |
| 可讀性 | 較差 | 較好 |
| 二進制數據支持 | 有限 | 更好 |
## 2. 基本用法
### 2.1 創建PreparedStatement
```java
Connection conn = DriverManager.getConnection(url, username, password);
String sql = "SELECT * FROM users WHERE id = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
PreparedStatement提供了一系列setXxx()方法來設置參數:
pstmt.setInt(1, 1001); // 設置第一個參數為整型值1001
pstmt.setString(2, "張三"); // 設置第二個參數為字符串
pstmt.setDate(3, new Date(System.currentTimeMillis())); // 設置日期參數
ResultSet rs = pstmt.executeQuery();
while(rs.next()) {
// 處理結果集
}
String updateSql = "UPDATE users SET name = ? WHERE id = ?";
PreparedStatement pstmt = conn.prepareStatement(updateSql);
pstmt.setString(1, "李四");
pstmt.setInt(2, 1001);
int affectedRows = pstmt.executeUpdate();
PreparedStatement支持批量操作,大幅提高大量數據操作的效率:
String insertSql = "INSERT INTO users(name, age) VALUES(?, ?)";
PreparedStatement pstmt = conn.prepareStatement(insertSql);
for(int i=0; i<1000; i++) {
pstmt.setString(1, "user"+i);
pstmt.setInt(2, 20+i%10);
pstmt.addBatch(); // 添加到批處理
if(i%100 == 0) { // 每100條執行一次
pstmt.executeBatch();
}
}
pstmt.executeBatch(); // 執行剩余的
當插入記錄需要獲取自增ID時:
String sql = "INSERT INTO users(name) VALUES(?)";
PreparedStatement pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
pstmt.setString(1, "王五");
pstmt.executeUpdate();
ResultSet rs = pstmt.getGeneratedKeys();
if(rs.next()) {
long id = rs.getLong(1);
System.out.println("生成的ID:" + id);
}
Java 7+推薦使用try-with-resources自動關閉資源:
String sql = "SELECT * FROM products WHERE price > ?";
try(Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setDouble(1, 100.0);
try(ResultSet rs = pstmt.executeQuery()) {
while(rs.next()) {
// 處理結果
}
}
} catch(SQLException e) {
e.printStackTrace();
}
數據庫會對PreparedStatement的SQL進行預編譯和緩存,當相同SQL多次執行時,只需設置不同的參數值,無需重新編譯,顯著提高性能。
最佳實踐是在應用生命周期內重用PreparedStatement:
// 初始化時
PreparedStatement userInsertStmt = conn.prepareStatement(INSERT_USER_SQL);
// 需要時重用
public void addUser(User user) throws SQLException {
userInsertStmt.setString(1, user.getName());
userInsertStmt.setInt(2, user.getAge());
userInsertStmt.executeUpdate();
}
對于大數據量查詢,設置fetchSize可以提高性能:
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setFetchSize(100); // 每次從數據庫獲取100條
PreparedStatement通過將參數值與SQL語句分離來防止注入:
// 安全,不會導致注入
String sql = "SELECT * FROM users WHERE name = ?";
pstmt.setString(1, name); // 即使name包含特殊字符也會被正確處理
不安全的方式:
// 危險!可能被SQL注入
String sql = "SELECT * FROM users WHERE name = '" + name + "'";
Statement stmt = conn.createStatement();
stmt.executeQuery(sql);
// 寫入BLOB
PreparedStatement pstmt = conn.prepareStatement("INSERT INTO files(data) VALUES(?)");
File file = new File("test.pdf");
try(FileInputStream fis = new FileInputStream(file)) {
pstmt.setBinaryStream(1, fis, (int)file.length());
pstmt.executeUpdate();
}
// 讀取BLOB
PreparedStatement pstmt = conn.prepareStatement("SELECT data FROM files WHERE id=?");
pstmt.setInt(1, fileId);
ResultSet rs = pstmt.executeQuery();
if(rs.next()) {
InputStream is = rs.getBinaryStream("data");
// 處理輸入流
}
// PostgreSQL數組示例
String sql = "INSERT INTO products (name, tags) VALUES (?, ?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "筆記本電腦");
String[] tags = {"電子","數碼","電腦"};
Array sqlArray = conn.createArrayOf("varchar", tags);
pstmt.setArray(2, sqlArray);
pstmt.executeUpdate();
Connection conn = null;
try {
conn = dataSource.getConnection();
conn.setAutoCommit(false); // 開始事務
PreparedStatement pstmt1 = conn.prepareStatement(UPDATE_ACCOUNT1_SQL);
PreparedStatement pstmt2 = conn.prepareStatement(UPDATE_ACCOUNT2_SQL);
// 執行第一個更新
pstmt1.setBigDecimal(1, transferAmount);
pstmt1.setInt(2, fromAccount);
pstmt1.executeUpdate();
// 執行第二個更新
pstmt2.setBigDecimal(1, transferAmount);
pstmt2.setInt(2, toAccount);
pstmt2.executeUpdate();
conn.commit(); // 提交事務
} catch(SQLException e) {
if(conn != null) conn.rollback(); // 回滾
throw e;
} finally {
if(conn != null) conn.close();
}
錯誤示例:
String sql = "SELECT * FROM users WHERE id = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(2, 1001); // 錯誤!只有1個參數卻試圖設置第2個
pstmt.setNull(1, Types.VARCHAR); // 明確設置NULL值
// 或者使用包裝類
Integer age = null; // 可能是NULL
pstmt.setObject(2, age); // 會自動處理NULL
// 使用java.time (Java 8+)
LocalDate date = LocalDate.now();
pstmt.setObject(1, date);
// 使用傳統java.sql.Date
pstmt.setDate(2, new java.sql.Date(System.currentTimeMillis()));
PreparedStatement是Java JDBC中強大且安全的數據訪問工具,它通過預編譯機制提高了性能,通過參數化查詢防止了SQL注入,并提供了豐富的API來處理各種數據類型。掌握PreparedStatement的正確用法是Java開發者進行數據庫編程的基礎技能,對于構建安全、高效的數據庫應用至關重要。
在實際開發中,雖然現在有許多ORM框架(如Hibernate、MyBatis)簡化了數據庫操作,但理解底層的PreparedStatement工作原理仍然非常必要,特別是在需要優化性能或處理復雜SQL場景時。 “`
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。