PreparedStatement 是 JDBC 中用于执行预编译 SQL 语句的接口,它继承自 Statement 接口,提供了更安全、更高效的 SQL 执行方式。
// 基本创建方式
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() 方法来设置参数:
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);
pstmt.setString(int parameterIndex, String x);
pstmt.setBytes(int parameterIndex, byte[] x);
pstmt.setBinaryStream(int parameterIndex, InputStream x);
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);
pstmt.setObject(int parameterIndex, Object x);
pstmt.setNull(int parameterIndex, int sqlType);
ResultSet rs = pstmt.executeQuery();
int rows = pstmt.executeUpdate();
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);
}
// 设置大文本
Reader reader = new StringReader("很长的文本内容...");
pstmt.setCharacterStream(1, reader);
// 设置二进制流
InputStream is = new FileInputStream("image.jpg");
pstmt.setBinaryStream(2, is);
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();
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
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类型
特性 | PreparedStatement | Statement |
---|---|---|
SQL注入防护 | 是 | 否 |
性能 | 高(预编译) | 低(每次编译) |
参数设置 | 参数化(?) | 字符串拼接 |
批处理 | 高效 | 效率较低 |
二进制数据 | 支持好 | 处理复杂 |
使用场景 | 重复执行相似SQL | 一次性执行动态SQL |
问题:参数索引越界或类型不匹配
try {
pstmt.setInt(1, 101);
} catch (SQLException e) {
// 检查SQL中的?数量和设置是否匹配
}
处理方式:
try {
int[] results = pstmt.executeBatch();
} catch (BatchUpdateException e) {
int[] partialResults = e.getUpdateCounts();
// 处理部分成功的情况
}
方案:
// 设置适当的fetchSize
pstmt.setFetchSize(500);
// 使用流式处理
pstmt.setFetchSize(Integer.MIN_VALUE);
PreparedStatement 是JDBC中最重要、最常用的接口之一,正确使用它可以显著提高应用程序的安全性、性能和可靠性。在实际开发中,应该优先使用PreparedStatement而不是Statement,特别是在处理用户输入或需要重复执行相似SQL时。