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注入风险:
实现步骤
- 定义自定义Repository接口
public interface FooBarCustomRepository { List<FooBar> findFooBarIn(List<FooBarTuple> fooBarTupleList); }
- 实现自定义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); } }
- 让原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
相关产品推荐
相关产品推荐

