您好,登录后才能下订单哦!
# 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进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。