第一部分:两种接口概述
理解Statement与PreparedStatement在JDBC中的角色与作用
学习目标
掌握两种接口的基本概念和适用场景
Statement接口
- JDBC中最基础的SQL执行接口
- 用于执行静态SQL语句
- 不支持参数化查询
- 每次执行都需要编译SQL
- 潜在SQL注入风险
PreparedStatement接口
- Statement的子接口
- 支持预编译SQL语句
- 使用参数占位符(?)
- 防止SQL注入攻击
- 提升执行性能
数据库
JDBC接口
VS
Java应用
核心概念
Statement用于执行不带参数的静态SQL语句,而PreparedStatement用于执行带参数的预编译SQL语句,可以更高效地多次执行相同结构的SQL。
第二部分:Statement详解
掌握Statement接口的基本使用
学习目标
学会使用Statement执行SQL查询和更新操作
1. 创建Statement对象
// 获取数据库连接
Connection conn = DriverManager.getConnection(url, username, password);
// 创建Statement对象
Statement stmt = conn.createStatement();
2. 执行查询操作
String sql = "SELECT id, name, email FROM users";
ResultSet rs = stmt.executeQuery(sql);
// 遍历结果集
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
String email = rs.getString("email");
System.out.println("ID: " + id + ", Name: " + name + ", Email: " + email);
}
3. 执行更新操作
String sqlUpdate = "UPDATE users SET email = 'new@example.com' WHERE id = 1";
int rowsAffected = stmt.executeUpdate(sqlUpdate);
System.out.println("更新的行数: " + rowsAffected);
SQL注入风险演示
// 恶意用户输入
String userInput = "1 OR 1=1; DROP TABLE users; --";
// 拼接SQL语句 - 高风险!
String sql = "DELETE FROM users WHERE id = " + userInput;
// 执行SQL - 后果严重!
stmt.executeUpdate(sql);
上述代码将导致删除整个users表!这正是SQL注入的典型示例。
第三部分:PreparedStatement详解
掌握预编译语句的使用和优势
学习目标
学会使用PreparedStatement执行安全的参数化查询
1. 创建PreparedStatement对象
// 使用带参数的SQL语句
String sql = "INSERT INTO users (name, email, age) VALUES (?, ?, ?)";
// 创建PreparedStatement对象
PreparedStatement pstmt = conn.prepareStatement(sql);
2. 设置参数值
// 设置参数值 (索引从1开始)
pstmt.setString(1, "张三"); // 设置name
pstmt.setString(2, "zhangsan@example.com"); // 设置email
pstmt.setInt(3, 30); // 设置age
// 执行更新
int rowsAffected = pstmt.executeUpdate();
System.out.println("插入的行数: " + rowsAffected);
参数类型方法
- setInt(int parameterIndex, int x) - 设置整数参数
- setString(int parameterIndex, String x) - 设置字符串参数
- setDouble(int parameterIndex, double x) - 设置双精度浮点数参数
- setDate(int parameterIndex, Date x) - 设置日期参数
- setBoolean(int parameterIndex, boolean x) - 设置布尔参数
- setNull(int parameterIndex, int sqlType) - 设置空值
3. 批量操作示例
String sql = "INSERT INTO products (name, price) VALUES (?, ?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
// 批量添加
pstmt.setString(1, "商品1");
pstmt.setDouble(2, 19.99);
pstmt.addBatch(); // 添加到批次
pstmt.setString(1, "商品2");
pstmt.setDouble(2, 29.99);
pstmt.addBatch();
pstmt.setString(1, "商品3");
pstmt.setDouble(2, 39.99);
pstmt.addBatch();
// 执行批量操作
int[] counts = pstmt.executeBatch();
System.out.println("添加的商品数量: " + counts.length);
第四部分:核心区别与对比
两种接口的全面对比分析
学习目标
理解两者的本质区别和应用场景
如何选择?
优先使用PreparedStatement - 在大多数业务场景下,特别是涉及用户输入时。
考虑使用Statement的情况:
- 执行DDL语句(如CREATE TABLE, ALTER TABLE等)
- 执行一次性查询
- 执行数据库管理操作(如备份、恢复等)
第五部分:安全性与性能
深入探讨PreparedStatement的安全与性能优势
学习目标
理解PreparedStatement的安全机制和性能优化原理
安全性机制
参数化查询防止SQL注入:
// 安全示例
String userInput = "1 OR 1=1; DROP TABLE users; --";
String sql = "SELECT * FROM users WHERE id = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, userInput); // 安全
数据库将把userInput
视为整体值,而不是SQL代码的一部分。
性能优化
预编译机制:
- SQL语句仅编译一次
- 多次执行只需参数绑定
- 利用数据库的执行计划缓存
- 减少网络传输量
性能对比测试
操作类型 | Statement执行时间(ms) | PreparedStatement执行时间(ms) | 性能提升 |
---|---|---|---|
1,000次INSERT | 2,450 | 420 | 5.8倍 |
10,000次SELECT | 3,120 | 560 | 5.6倍 |
批处理操作 | 1,890 | 310 | 6.1倍 |
性能测试结果来自对MySQL 8.0数据库的基准测试,使用相同的硬件配置和测试数据。
第六部分:实践应用
在实际开发中的最佳实践
学习目标
掌握在实际项目中正确使用Statement和PreparedStatement
完整CRUD操作示例
public class UserDao {
private Connection conn;
public UserDao(Connection conn) {
this.conn = conn;
}
// 添加用户 - PreparedStatement
public void addUser(User user) throws SQLException {
String sql = "INSERT INTO users (name, email, age) VALUES (?, ?, ?)";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, user.getName());
pstmt.setString(2, user.getEmail());
pstmt.setInt(3, user.getAge());
pstmt.executeUpdate();
}
}
// 获取用户 - PreparedStatement
public User getUser(int id) throws SQLException {
String sql = "SELECT * FROM users WHERE id = ?";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, id);
try (ResultSet rs = pstmt.executeQuery()) {
if (rs.next()) {
return new User(
rs.getInt("id"),
rs.getString("name"),
rs.getString("email"),
rs.getInt("age")
);
}
}
}
return null;
}
// 创建表 - Statement
public void createTable() throws SQLException {
String sql = "CREATE TABLE IF NOT EXISTS users (" +
"id INT AUTO_INCREMENT PRIMARY KEY, " +
"name VARCHAR(100) NOT NULL, " +
"email VARCHAR(100) UNIQUE, " +
"age INT DEFAULT 0)";
try (Statement stmt = conn.createStatement()) {
stmt.executeUpdate(sql);
}
}
}
最佳实践
- 使用PreparedStatement处理所有用户输入
- 使用try-with-resources确保资源关闭
- 为参数设置正确的JDBC类型
- 避免在循环中创建PreparedStatement
- 对批量操作使用addBatch()方法
- 连接关闭前关闭所有Statement对象
常见错误
- 忘记关闭资源 - 导致连接泄漏
- 参数索引错误 - 索引从1开始,不是0
- 数据类型不匹配 - 字符串设到数值类型字段
- SQL中缺少参数占位符 - SQL与参数不匹配
- 重复创建PreparedStatement - 应该重用
总结要点
- PreparedStatement是处理用户输入的首选方案
- Statement适用于执行静态SQL和DDL操作
- PreparedStatement显著提升性能并保证安全
- 合理使用批处理操作优化数据库性能
- 始终使用try-with-resources管理资源