You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Data JDBC如何实现多列IN子句的元组参数查询?

问题:Spring Data JDBC 多列IN子句的元组参数查询支持及实现

实体定义

public class FooBar{
    @Id
    private long id;
    private Long fooNumber;
    private Long barNumber;
    ...
    //Other inconsequential properties
}

期望执行的SQL

SELECT * FROM foo_bar WHERE (foo_number, bar_number) IN ((1,2),(1,4),(99,45))

需要IN子句接收任意元组列表作为参数,类似:

SELECT * FROM foo_bar WHERE (fooNumber, barNumber) IN :List(FooBarTuple)

尝试的实现代码

Repository层

@Repository
public interface FooBarRepository extends CrudRepository<FooBar, Long> {

    @Query("SELECT id, foo_number, bar_number FROM foo_bar WHERE (foo_number, bar_number) IN :fooBarList;")
    List<FooBar> findFooBarIn(@Param("fooBarList") List<FooBarTuple> fooBarList);
}

元组类

public class FooBarTuple {
    private Long fooNumber;
    private Long barNumber;
}

Service层

@Service
public class FooBarService {
    //Repository inject, other methods

    public List<FooBar> getFooBar(List<FooBarTuple> fooBarTupleList) {
        return fooBarRepository.findFooBarIn(fooBarTupleList);
    }
}

执行异常及生成的SQL

执行时抛出异常:

org.postgresql.util.PSQLException: ERROR: syntax error at or near "$1"

Spring Boot生成的SQL:

Executing prepared SQL statement [SELECT id, foo_number, bar_number FROM foo_bar WHERE (foo_number, bar_number) IN ?, ?, ?;]

提问:在Spring Data JDBC(非JPA)中是否支持多列IN子句的元组参数查询?若支持,求实现示例。


解决方案

Spring Data JDBC 支持多列IN子句的元组参数查询,但直接传入元组列表会导致参数绑定格式错误,需要调整实现方式,以下是两种可行方案:

方案1:使用NamedParameterJdbcTemplate手动构建查询

通过手动拼接元组占位符并绑定参数,避免Spring Data JDBC自动拆分列表参数的问题,同时完全规避SQL注入风险:

实现步骤

  1. 定义自定义Repository接口
public interface FooBarCustomRepository {
    List<FooBar> findFooBarIn(List<FooBarTuple> fooBarTupleList);
}
  1. 实现自定义Repository
@Repository
public class FooBarRepositoryImpl implements FooBarCustomRepository {

    private final NamedParameterJdbcTemplate jdbcTemplate;
    private final RowMapper<FooBar> fooBarRowMapper = (rs, rowNum) -> {
        FooBar fooBar = new FooBar();
        fooBar.setId(rs.getLong("id"));
        fooBar.setFooNumber(rs.getLong("foo_number"));
        fooBar.setBarNumber(rs.getLong("bar_number"));
        // 映射其他需要的字段
        return fooBar;
    };

    public FooBarRepositoryImpl(NamedParameterJdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public List<FooBar> findFooBarIn(List<FooBarTuple> fooBarTupleList) {
        if (fooBarTupleList.isEmpty()) {
            return Collections.emptyList();
        }

        StringBuilder inClause = new StringBuilder();
        Map<String, Object> params = new HashMap<>();

        // 为每个元组生成独立的参数占位符
        for (int i = 0; i < fooBarTupleList.size(); i++) {
            FooBarTuple tuple = fooBarTupleList.get(i);
            String paramPrefix = "tuple" + i;
            inClause.append("(:").append(paramPrefix).append("_foo, :").append(paramPrefix).append("_bar)");
            if (i != fooBarTupleList.size() - 1) {
                inClause.append(", ");
            }
            params.put(paramPrefix + "_foo", tuple.getFooNumber());
            params.put(paramPrefix + "_bar", tuple.getBarNumber());
        }

        String sql = "SELECT id, foo_number, bar_number FROM foo_bar WHERE (foo_number, bar_number) IN (" + inClause + ")";
        return jdbcTemplate.query(sql, params, fooBarRowMapper);
    }
}
  1. 让原Repository继承自定义接口
public interface FooBarRepository extends CrudRepository<FooBar, Long>, FooBarCustomRepository {
}

方案2:使用Spring Data JDBC的@Query配合SpEL表达式(适用于2.4+版本)

利用SpEL动态构建元组格式的IN子句,写法更简洁,但需确保参数为可信的数值类型(避免SQL注入):

@Repository
public interface FooBarRepository extends CrudRepository<FooBar, Long> {

    @Query("""
        SELECT id, foo_number, bar_number FROM foo_bar 
        WHERE (foo_number, bar_number) IN (
            :#{#fooBarList.stream().map(t -> '(' + t.fooNumber + ',' + t.barNumber + ')').collect(java.util.stream.Collectors.joining(','))}
        )
    """)
    List<FooBar> findFooBarIn(@Param("fooBarList") List<FooBarTuple> fooBarList);
}

关键说明

  • Spring Data JDBC默认会把列表参数拆分为多个独立的?占位符,但多列IN子句需要的是(?,?), (?,?)的元组格式,这是导致原代码报错的核心原因。
  • 方案1是生产环境推荐的安全写法,通过参数绑定彻底避免SQL注入;方案2更简洁,但仅适用于参数为可信数值的场景。

内容的提问来源于stack exchange,提问作者jspillers

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 03:22:52