PreparedStatement 和 Statement的区别
-
-
- [1. 代码可读性与可维护性](#1. 代码可读性与可维护性)
- [2. 性能与预编译](#2. 性能与预编译)
- [3. 安全性:防止 SQL 注入](#3. 安全性:防止 SQL 注入)
- [4. 数据类型处理](#4. 数据类型处理)
- [5. 适用范围](#5. 适用范围)
- [6. 批量操作的支持](#6. 批量操作的支持)
- 总结对比表
-
PreparedStatement 和 Statement 都是 Java JDBC 中用来执行 SQL 语句的接口,但它们在性能、安全性、可读性 以及功能 上有着显著的区别。在实际开发中,通常推荐使用 PreparedStatement。
以下是它们之间区别的详细展开说明:
1. 代码可读性与可维护性
-
Statement: 需要进行字符串拼接,尤其是当参数较多时,代码会变得冗长且难以阅读。
java// 需要手动拼接引号 int id = 1; String name = "John"; String sql = "SELECT * FROM users WHERE id = " + id + " AND name = '" + name + "'"; Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery(sql); -
PreparedStatement: 使用占位符
?,参数与 SQL 语句分离,结构清晰。javaString sql = "SELECT * FROM users WHERE id = ? AND name = ?"; PreparedStatement pstmt = connection.prepareStatement(sql); pstmt.setInt(1, id); // 参数索引从1开始 pstmt.setString(2, name); ResultSet rs = pstmt.executeQuery(); // 这里不需要再传入sql
2. 性能与预编译
这是两者最核心的性能差异。
-
Statement: 每次执行
executeQuery()或executeUpdate()时,数据库系统都会对该 SQL 语句进行语法检查、语义检查、生成执行计划 。即使两次执行的 SQL 语句结构完全一样,只是参数不同(例如WHERE id = 1和WHERE id = 2),数据库也会把它们当作两个全新的语句来编译,不会重用之前的执行计划,导致效率较低。 -
PreparedStatement: 创建时会将 SQL 语句发送给数据库进行预编译。数据库一旦编译完成,就会将其缓存起来。之后无论传入什么参数,数据库都直接使用已编译好的执行计划,传入参数即可执行。
- 优势: 在需要反复执行同一条 SQL 语句(如批量插入、频繁查询)时,可以节省每次编译的开销,显著提升性能。
3. 安全性:防止 SQL 注入
这是使用 PreparedStatement 最重要的理由。
-
Statement: 由于使用字符串拼接,攻击者可以利用输入数据修改原有的 SQL 逻辑。
-
例子: 假设用户输入用户名作为参数,如果用户输入
' OR '1'='1,拼接后的 SQL 可能变成:sqlSELECT * FROM users WHERE name = '' OR '1'='1' -- 恒成立,导致越权查询所有用户
-
-
PreparedStatement: 能够有效防止 SQL 注入。
- 原因:
PreparedStatement在处理参数时,会对传入的参数进行转义 。它将传入的参数严格视为数据 ,而不是 SQL 关键字的一部分 。即使参数中包含 SQL 指令(如OR 1=1),数据库也只会把它当作普通的字符串值来处理,而不会去执行其中的逻辑。
- 原因:
4. 数据类型处理
- Statement: 所有参数都需要手动转换为字符串类型进行拼接,容易因格式问题引发错误,尤其是在处理日期、时间戳、二进制数据时。
- PreparedStatement: 提供了丰富的
setXxx()方法(如setDate(),setTimestamp(),setBigDecimal()),能够更精确地将 Java 对象映射到对应的 SQL 数据类型,减少类型转换带来的错误。
5. 适用范围
- Statement: 更适合执行不需要参数的 DDL(数据定义语言)语句 ,例如
CREATE TABLE,ALTER TABLE,或者执行一次性的、没有参数的 SQL。 - PreparedStatement: 适用于需要传入参数的 DML(数据操作语言)语句 (
SELECT,INSERT,UPDATE,DELETE),尤其是需要批量执行的场景。
6. 批量操作的支持
-
PreparedStatement: 对于批量操作有很好的支持。可以使用
addBatch()设置不同的参数,然后一次性通过executeBatch()提交给数据库执行,从而减少网络交互次数。java// 预编译一次 PreparedStatement pstmt = conn.prepareStatement("INSERT INTO users (id, name) VALUES (?, ?)"); for (User user : userList) { pstmt.setInt(1, user.getId()); pstmt.setString(2, user.getName()); pstmt.addBatch(); // 添加到批次 } pstmt.executeBatch(); // 一次性批量执行
总结对比表
| 特性 | Statement | PreparedStatement | 结论 |
|---|---|---|---|
| 预编译 | 不预编译,每次执行都要编译 | 预编译,通常只编译一次 | PreparedStatement 性能更好 |
| SQL 注入 | 存在严重安全漏洞 | 自动防止 SQL 注入 | PreparedStatement 更安全 |
| 可读性 | 参数拼接,混乱易错 | 参数占位符,结构清晰 | PreparedStatement 更清晰 |
| 性能 | 低(尤其是循环执行时) | 高(预编译+缓存执行计划) | PreparedStatement 更高效 |
| 适用场景 | 表名/结构变化,DDL | 数据操作,带参数的增删改查 | 各有所长,但 DML 多用后者 |
总的来说,在开发中,除非是执行结构变化的 DDL 语句,否则都应该优先使用 PreparedStatement。