PreparedStatement详解
PreparedStatement 详解
一、PreparedStatement 概述
PreparedStatement 是 JDBC 中用于执行预编译 SQL 语句的接口,它继承自 Statement 接口,提供了更安全、更高效的 SQL 执行方式。
主要特点:
- 预编译:SQL 语句被预编译并存储在 PreparedStatement 对象中
- 参数化查询:使用占位符(?)代替直接拼接SQL值
- 防止SQL注入:自动处理特殊字符,提高安全性
- 性能优势:可重复使用,减少数据库编译开销
二、创建 PreparedStatement
// 基本创建方式
String sql = "SELECT * FROM users WHERE id = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);// 可指定结果集类型和并发模式
PreparedStatement pstmt = connection.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY);// 可指定是否返回自动生成的主键
PreparedStatement pstmt = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
三、参数设置方法
PreparedStatement 提供了一系列 setXxx() 方法来设置参数:
1. 基本数据类型
pstmt.setInt(int parameterIndex, int x);
pstmt.setLong(int parameterIndex, long x);
pstmt.setFloat(int parameterIndex, float x);
pstmt.setDouble(int parameterIndex, double x);
pstmt.setBoolean(int parameterIndex, boolean x);
2. 字符串和二进制数据
pstmt.setString(int parameterIndex, String x);
pstmt.setBytes(int parameterIndex, byte[] x);
pstmt.setBinaryStream(int parameterIndex, InputStream x);
3. 日期和时间
pstmt.setDate(int parameterIndex, Date x);
pstmt.setTime(int parameterIndex, Time x);
pstmt.setTimestamp(int parameterIndex, Timestamp x);// 使用Calendar指定时区
pstmt.setDate(int parameterIndex, Date x, Calendar cal);
4. 其他类型
pstmt.setObject(int parameterIndex, Object x);
pstmt.setNull(int parameterIndex, int sqlType);
参数索引说明:
- 参数索引从1开始,不是0
- 每个?占位符必须设置值
- 可以多次设置同一参数的值(会覆盖前值)
四、执行 PreparedStatement
1. 执行查询(返回ResultSet)
ResultSet rs = pstmt.executeQuery();
2. 执行更新(返回影响行数)
int rows = pstmt.executeUpdate();
3. 执行任意SQL(可能返回多个结果)
boolean hasResultSet = pstmt.execute();
if (hasResultSet) {ResultSet rs = pstmt.getResultSet();
}
五、批处理操作
PreparedStatement 特别适合批处理操作:
String sql = "INSERT INTO products (id, name, price) VALUES (?, ?, ?)";
PreparedStatement pstmt = connection.prepareStatement(sql);for (Product product : productList) {pstmt.setInt(1, product.getId());pstmt.setString(2, product.getName());pstmt.setDouble(3, product.getPrice());pstmt.addBatch(); // 添加到批处理// 每100条执行一次,避免内存溢出if (i % 100 == 0) {pstmt.executeBatch();}
}// 执行剩余的批处理
int[] result = pstmt.executeBatch();// 清空批处理
pstmt.clearBatch();
六、获取自动生成的主键
String sql = "INSERT INTO users (name, age) VALUES (?, ?)";
PreparedStatement pstmt = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);pstmt.setString(1, "张三");
pstmt.setInt(2, 25);
pstmt.executeUpdate();// 获取生成的主键
ResultSet rs = pstmt.getGeneratedKeys();
if (rs.next()) {long id = rs.getLong(1);System.out.println("生成的主键ID: " + id);
}
七、PreparedStatement 高级特性
1. 流式处理大数据
// 设置大文本
Reader reader = new StringReader("很长的文本内容...");
pstmt.setCharacterStream(1, reader);// 设置二进制流
InputStream is = new FileInputStream("image.jpg");
pstmt.setBinaryStream(2, is);
2. 可滚动/可更新的ResultSet
PreparedStatement pstmt = connection.prepareStatement("SELECT * FROM employees WHERE dept = ?",ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_UPDATABLE);pstmt.setString(1, "IT");
ResultSet rs = pstmt.executeQuery();// 更新结果集
rs.last();
rs.updateString("name", "New Name");
rs.updateRow();
3. 参数元数据
ParameterMetaData pmd = pstmt.getParameterMetaData();
int paramCount = pmd.getParameterCount();
for (int i = 1; i <= paramCount; i++) {String typeName = pmd.getParameterTypeName(i);System.out.println("参数" + i + "类型: " + typeName);
}
八、PreparedStatement 最佳实践
-
重用PreparedStatement:在循环外创建,循环内重复使用
// 错误做法 - 每次循环都创建新的PreparedStatement for (User user : users) {PreparedStatement pstmt = conn.prepareStatement(...);// ... }// 正确做法 - 重用PreparedStatement PreparedStatement pstmt = conn.prepareStatement(...); for (User user : users) {pstmt.setString(1, user.getName());// ...pstmt.executeUpdate(); }
-
总是关闭资源:使用try-with-resources
try (PreparedStatement pstmt = connection.prepareStatement(sql)) {pstmt.setInt(1, 101);try (ResultSet rs = pstmt.executeQuery()) {// 处理结果} }
-
合理设置fetchSize:大数据量查询时提高性能
PreparedStatement pstmt = connection.prepareStatement(sql); pstmt.setFetchSize(1000); // 每次从数据库获取1000条
-
设置查询超时:避免长时间运行的查询
pstmt.setQueryTimeout(30); // 30秒超时
-
处理NULL值:
pstmt.setNull(1, Types.VARCHAR); // 明确设置NULL类型
九、与Statement的比较
特性 | PreparedStatement | Statement |
---|---|---|
SQL注入防护 | 是 | 否 |
性能 | 高(预编译) | 低(每次编译) |
参数设置 | 参数化(?) | 字符串拼接 |
批处理 | 高效 | 效率较低 |
二进制数据 | 支持好 | 处理复杂 |
使用场景 | 重复执行相似SQL | 一次性执行动态SQL |
十、常见问题解决方案
1. 参数设置错误
问题:参数索引越界或类型不匹配
try {pstmt.setInt(1, 101);
} catch (SQLException e) {// 检查SQL中的?数量和设置是否匹配
}
2. 批处理部分失败
处理方式:
try {int[] results = pstmt.executeBatch();
} catch (BatchUpdateException e) {int[] partialResults = e.getUpdateCounts();// 处理部分成功的情况
}
3. 大数据量处理
方案:
// 设置适当的fetchSize
pstmt.setFetchSize(500);// 使用流式处理
pstmt.setFetchSize(Integer.MIN_VALUE);
PreparedStatement 是JDBC中最重要、最常用的接口之一,正确使用它可以显著提高应用程序的安全性、性能和可靠性。在实际开发中,应该优先使用PreparedStatement而不是Statement,特别是在处理用户输入或需要重复执行相似SQL时。