杰瑞科技汇

Java SQL IN占位符怎么用?

什么是占位符?

在 Java 中,当我们需要执行一条带有动态数据的 SQL 语句时(根据用户输入的用户名查询信息),我们不能直接将变量拼接在 SQL 字符串里,这样做不仅代码难看,而且极其危险(SQL 注入)。

Java SQL IN占位符怎么用?-图1
(图片来源网络,侵删)

占位符就是 SQL 语句中的一个特殊标记,它代表一个“占位”,等待后续用实际的变量值来填充,它像一个模具,我们先用它定义好 SQL 的结构,然后再“注入”具体的数据。

在 Java 中,最常用的占位符有两种,它们分别对应不同的数据库 API:

  1. (问号):用于 JDBC (Java Database Connectivity),这是 Java 访问数据库的官方标准 API。
  2. (井号加花括号):用于 MyBatis 等持久层框架,它是对 JDBC 的一种高级封装。

JDBC 中的占位符

这是最基础、最核心的用法,当你使用原生 JDBC 时, 是你唯一的选择。

工作原理

  1. 编写 SQL 模板:在 SQL 字符串中,用 来表示需要动态替换的部分。
  2. 创建 PreparedStatement:使用 Connection 对象的 prepareStatement() 方法,传入 SQL 模板,这会创建一个 PreparedStatement 对象,数据库会预先编译这个 SQL 模板。
  3. 设置参数:通过 PreparedStatementsetXxx() 方法(如 setString(), setInt() 等),按照 的顺序(从 1 开始)将实际的值绑定到占位符上。
  4. 执行 SQL:调用 executeQuery()executeUpdate() 方法来执行 SQL,数据库会使用你提供的值来执行预编译好的语句。

代码示例

假设我们有一个 users 表,结构如下: id (INT), username (VARCHAR), password (VARCHAR)

Java SQL IN占位符怎么用?-图2
(图片来源网络,侵删)

目标:根据用户名 username 查询用户信息。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JdbcPlaceholderExample {
    // 数据库连接信息 (请替换为你自己的)
    private static final String DB_URL = "jdbc:mysql://localhost:3306/your_database";
    private static final String USER = "your_username";
    private static final String PASS = "your_password";
    public static void main(String[] args) {
        String usernameToFind = "john_doe"; // 这是用户输入的动态值
        // 1. 编写 SQL 模板,使用 ? 作为占位符
        String sql = "SELECT id, username, password FROM users WHERE username = ?";
        // try-with-resources 语句,确保资源自动关闭
        try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            // 2. 设置参数,将 usernameToFind 的值绑定到第一个 ? 上
            // 参数索引从 1 开始
            pstmt.setString(1, usernameToFind);
            // 3. 执行查询
            ResultSet rs = pstmt.executeQuery();
            // 处理结果集
            if (rs.next()) {
                int id = rs.getInt("id");
                String username = rs.getString("username");
                String password = rs.getString("password");
                System.out.println("找到用户: ID=" + id + ", 用户名=" + username + ", 密码=" + password);
            } else {
                System.out.println("未找到用户名为 '" + usernameToFind + "' 的用户。");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

为什么必须使用 而不是字符串拼接?

危险示例:字符串拼接

// !!! 危险的代码,绝对不要这样做 !!!
String userInput = "admin' OR '1'='1"; // 恶意的用户输入
String sql = "SELECT * FROM users WHERE username = '" + userInput + "'";
// 最终的 SQL 变成了: SELECT * FROM users WHERE username = 'admin' OR '1'='1'
// 这个条件永远为真,导致查询出所有用户!这就是 SQL 注入攻击。

安全示例:使用

String userInput = "admin' OR '1'='1"; // 恶意的用户输入
String sql = "SELECT * FROM users WHERE username = ?";
// ...
pstmt.setString(1, userInput);
// 最终发送给数据库的 SQL 始终是: SELECT * FROM users WHERE username = 'admin\' OR \'1\'=\'1'
// 数据库会将整个字符串 "admin' OR '1'='1" 当作一个普通的用户名去精确匹配,而不是作为 SQL 代码执行。
// 因为 SQL 语句的结构已经被预编译和固定了。

的优点:

Java SQL IN占位符怎么用?-图3
(图片来源网络,侵删)
  • 防止 SQL 注入:这是最重要的优点。PreparedStatement 会将参数值作为字面量处理,而不是 SQL 代码的一部分。
  • 性能提升:对于需要多次执行的 SQL(只是参数不同),数据库可以缓存预编译的 SQL 语句,提高执行效率。
  • 代码清晰:将 SQL 逻辑和数据绑定逻辑分离,使代码更易读和维护。

MyBatis 中的占位符 和

MyBatis 是一个流行的持久层框架,它简化了 JDBC 操作,MyBatis 提供了两种占位符,它们的作用有本质区别。

(参数占位符)

这是 MyBatis 中推荐使用的默认占位符。

  • 工作原理: 会被 MyBatis 预编译成一个 ,然后通过 PreparedStatement 设置参数,它完全具备 的所有优点,可以 100% 防止 SQL 注入
  • 场景:适用于所有需要传入变量值的地方,如 WHERE 条件、INSERT 的值、UPDATE 的值等。

MyBatis XML 示例 (使用 )

<!-- UserMapper.xml -->
<select id="findUserByUsername" resultType="User">
  SELECT id, username, password
  FROM users
  WHERE username = #{username}
</select>

当调用 mapper.findUserByUsername("john_doe") 时,MyBatis 会将其转换为 JDBC 的 PreparedStatement,并调用 pstmt.setString(1, "john_doe")

(字符串替换/拼接)

这是一个非常强大但极其危险的占位符,必须谨慎使用。

  • 工作原理: 不会进行预编译,而是将变量直接替换成字符串,拼接到最终的 SQL 语句中,它相当于 Java 里的字符串拼接。
  • 场景
    1. 表名或列名是动态的:根据配置选择不同的表进行查询。SELECT * FROM ${tableName}
    2. SQL 片段的动态拼接ORDER BY 后面跟动态的列名。ORDER BY ${columnName}
  • 警告:因为 是直接拼接字符串,所以无法防止 SQL 注入,如果输入的数据来自用户,必须进行严格的白名单校验。

MyBatis XML 示例 (使用 ,危险!)

<!-- 假设 orderByColumn 是用户输入的值 -->
<select id="findUsersOrdered" resultType="User">
  SELECT id, username, password
  FROM users
  ORDER BY ${orderByColumn} <!-- 危险!orderByColumn 是 "id; DROP TABLE users;--" -->
</select>

orderByColumn 的值是 "id; DROP TABLE users;--",最终生成的 SQL 会是: SELECT id, username, password FROM users ORDER BY id; DROP TABLE users;-- 这将导致表被删除!


总结与对比

特性 JDBC (问号) MyBatis (井号) MyBatis (美元符)
核心机制 预编译,参数化查询 预编译,参数化查询 字符串拼接,直接替换
安全性 高,防止 SQL 注入 高,防止 SQL 注入 低,存在 SQL 注入风险
性能 高(预编译缓存) 高(底层是 ) 低(每次都要重新解析 SQL)
主要用途 所有动态数据 所有动态数据(首选) 动态表名、动态列名、SQL 片段
最佳实践 必须使用 必须使用 仅在无法使用 时使用,并严格校验输入

最终建议:

  1. 如果你使用原生 JDBC:永远、永远、永远使用 。
  2. 如果你使用 MyBatis 或类似框架
    • 默认且优先使用 ,它能解决 99% 的动态数据问题,并且是安全的。
    • 仅在万不得已的情况下(如动态表名)使用 ,并且要对输入的数据进行极其严格的验证(只允许预定义的、固定的几个值)。
分享:
扫描分享到社交APP
上一篇
下一篇