IN 操作符允许你在 WHERE 子句中指定多个值,它极大地简化了查询多个条件的 SQL 语句,避免了使用多个 OR。

IN 查询的基本 SQL 语法
我们看一下 IN 查询在 SQL 中的基本形式:
SELECT column_name(s) FROM table_name WHERE column_name IN (value1, value2, ...);
查询 id 为 1, 3, 5 的用户:
SELECT id, name, email FROM users WHERE id IN (1, 3, 5);
这等价于:
SELECT id, name, email FROM users WHERE id = 1 OR id = 3 OR id = 5;
在 Java 中实现 IN 查询
在 Java 中,执行 SQL 查询主要有两种方式:

- 使用
PreparedStatement(推荐):这是最安全、最高效的方式,可以防止 SQL 注入。 - 使用字符串拼接 (不推荐):有严重的 SQL 注入风险,应尽量避免。
下面我们重点介绍使用 PreparedStatement 的几种方法。
准备工作:数据库和依赖
假设我们有一个 users 表:
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE
);
INSERT INTO users (name, email) VALUES
('Alice', 'alice@example.com'),
('Bob', 'bob@example.com'),
('Charlie', 'charlie@example.com'),
('David', 'david@example.com');
在 Java 项目中,你需要添加 MySQL 驱动依赖,如果你使用 Maven,pom.xml 文件如下:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version> <!-- 使用与你的MySQL版本兼容的版本 -->
</dependency>
方法一:为 IN 中的每个值设置一个占位符 (适用于少量值)
这是最直接的方法。IN 子句中有多少个值,就在 PreparedStatement 中设置多少个 占位符。

示例代码:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
public class InQueryExample {
// 数据库连接信息
private static final String DB_URL = "jdbc:mysql://localhost:3306/your_database?useSSL=false&serverTimezone=UTC";
private static final String USER = "your_username";
private static final String PASS = "your_password";
public static void main(String[] args) {
// 要查询的ID列表
List<Integer> ids = Arrays.asList(1, 3, 5);
// SQL语句,为每个id准备一个占位符
String sql = "SELECT id, name, email FROM users WHERE id IN (?, ?, ?)";
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
// 设置占位符的值
for (int i = 0; i < ids.size(); i++) {
pstmt.setInt(i + 1, ids.get(i)); // setXXX的索引从1开始
}
System.out.println("Executing SQL: " + pstmt.toString());
try (ResultSet rs = pstmt.executeQuery()) {
System.out.println("查询结果:");
while (rs.next()) {
// 通过列名获取数据,更具可读性
int id = rs.getInt("id");
String name = rs.getString("name");
String email = rs.getString("email");
System.out.printf("ID: %d, Name: %s, Email: %s%n", id, name, email);
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
优点:
- 简单直观。
缺点:
- 不灵活:
ids列表的长度是动态变化的,你就需要动态构建 SQL 字符串,这很麻烦且容易出错。
方法二:使用 FIND_IN_SET (适用于MySQL,但效率较低)
FIND_IN_SET(str, strlist) 函数返回在逗号分隔的字符串列表 strlist 中找到 str 的位置,如果找到,返回 1,否则返回 0。
SQL 语法:
SELECT id, name, email FROM users WHERE FIND_IN_SET(id, '1,3,5');
在 Java 中,我们可以将 List 拼接成一个逗号分隔的字符串。
示例代码:
import java.sql.*;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class FindInSetExample {
private static final String DB_URL = "jdbc:mysql://localhost:3306/your_database?useSSL=false&serverTimezone=UTC";
private static final String USER = "your_username";
private static final String PASS = "your_password";
public static void main(String[] args) {
List<Integer> ids = Arrays.asList(1, 3, 5);
// 将List<Integer>转换为逗号分隔的字符串 "1,3,5"
String idString = ids.stream()
.map(String::valueOf)
.collect(Collectors.joining(","));
// SQL语句
String sql = "SELECT id, name, email FROM users WHERE FIND_IN_SET(id, ?)";
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
// 设置占位符
pstmt.setString(1, idString);
System.out.println("Executing SQL: " + pstmt.toString());
try (ResultSet rs = pstmt.executeQuery()) {
System.out.println("查询结果:");
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
String email = rs.getString("email");
System.out.printf("ID: %d, Name: %s, Email: %s%n", id, name, email);
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
优点:
- 只需要一个占位符,代码更简洁,适合动态长度的列表。
缺点:
- 性能较差:
FIND_IN_SET会进行全表扫描,无法使用id列上的索引,当数据量大时,查询速度会非常慢。不推荐用于大数据量的查询。
方法三:使用 JOIN 和 IN (最推荐的高性能方法)
这是处理动态 IN 列表最推荐的方法,尤其是在使用像 MyBatis 或 JPA 这样的 ORM 框架时,它将 IN 查询转换为一个 JOIN 操作,可以高效地利用索引。
核心思想:
- 创建一个临时表(或内存表),将要查询的值(如
1, 3, 5)存入其中。 - 将主表与这个临时表进行
JOIN关联。
示例代码:
import java.sql.*;
import java.util.Arrays;
import java.util.List;
public class JoinInSetExample {
private static final String DB_URL = "jdbc:mysql://localhost:3306/your_database?useSSL=false&serverTimezone=UTC";
private static final String USER = "your_username";
private static final String PASS = "your_password";
public static void main(String[] args) {
List<Integer> ids = Arrays.asList(1, 3, 5);
// 1. 创建一个临时表来存放ID
String createTempTableSql = "CREATE TEMPORARY TABLE temp_ids (id INT PRIMARY KEY)";
// 2. 向临时表中插入数据
String insertTempSql = "INSERT INTO temp_ids (id) VALUES (?)";
// 3. 使用JOIN查询
String selectSql = "SELECT u.id, u.name, u.email FROM users u JOIN temp_ids t ON u.id = t.id";
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS)) {
// 关闭自动提交,以便在一个事务中执行多个语句
conn.setAutoCommit(false);
// 步骤1: 创建临时表
try (Statement stmt = conn.createStatement()) {
stmt.execute(createTempTableSql);
}
// 步骤2: 批量插入数据到临时表
try (PreparedStatement pstmt = conn.prepareStatement(insertTempSql)) {
for (Integer id : ids) {
pstmt.setInt(1, id);
pstmt.addBatch(); // 添加到批处理
}
pstmt.executeBatch(); // 执行批处理
}
// 步骤3: 执行JOIN查询
try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(selectSql)) {
System.out.println("查询结果:");
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
String email = rs.getString("email");
System.out.printf("ID: %d, Name: %s, Email: %s%n", id, name, email);
}
}
// 提交事务
conn.commit();
} catch (SQLException e) {
e.printStackTrace();
// 如果出错,回滚事务
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS)) {
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}
}
优点:
- 性能极高:
JOIN操作可以利用id列上的索引,查询速度非常快,不受IN列表长度的影响。 - 代码结构清晰:将数据准备和查询逻辑分离。
缺点:
- 需要执行多个 SQL 语句,代码相对复杂一些。
总结与最佳实践
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 多个占位符 | 简单,安全,性能好 | 不灵活,无法处理动态长度的列表 | IN 列表长度固定且很少变化的情况。 |
FIND_IN_SET |
代码简洁,一个占位符 | 性能差,无法使用索引,全表扫描 | 列表很短,或者对性能要求不高的场景。尽量避免。 |
JOIN 临时表 |
性能最高,可处理任意长度的列表 | 代码复杂,需要多个SQL语句 | 强烈推荐,特别是当 IN 列表是动态生成且可能很长时。 |
最终建议:
IN列表是固定的、已知的,使用方法一(多个占位符),它最简单直接。IN列表是动态变化的,强烈推荐使用方法三(JOIN临时表),以获得最佳性能。- 永远不要在 Java 代码中通过字符串拼接来构建
IN子句,以防止 SQL 注入。
在现代 Java 开发中,通常会使用 MyBatis、JPA (Hibernate) 等 ORM 框架,它们内部已经实现了高效处理动态 IN 查询的机制(通常就是基于 JOIN 的思想),开发者只需调用相应的方法即可,无需关心底层的 SQL 实现。
