Spring JDBC绑定IN子句参数如何规避SQL注入同时避免MySQL预编译缓存过多
关于禁用预编译的可行性
你可以通过修改JDBC连接配置禁用服务端预编译缓存,在连接URL中添加参数cachePrepStmts=false即可,该配置会让MySQL服务端不缓存PreparedStatement,即使IN子句参数数量不同生成了不同的预编译语句,也不会触发缓存数量超限的报错。但该方案会导致每次执行SQL都需要重新解析,存在一定的性能损耗。
不建议完全禁用预编译语句生成,直接拼接未转义的字符串会引入SQL注入风险。
更推荐的优化方案(兼顾安全与性能)
1. IN参数数量分桶对齐
将IN参数的数量对齐到固定的几档(比如按2的幂次:4、8、16、32、64,超过64就分批查询),不够的参数位补和最后一位相同的业务有效值,或者补不存在于业务范围内的占位值。
该方案可以把预编译语句的数量控制在个位数,完全不会触发缓存超限,性能损耗可忽略:
List<String> addressList = Arrays.asList("Canada", "USA", "Finland"); int originalSize = addressList.size(); // 对齐到最近的2的幂次,最大批次设为64 int targetSize = Integer.highestOneBit(originalSize) == originalSize ? originalSize : Integer.highestOneBit(originalSize) << 1; targetSize = Math.min(targetSize, 64); // 补全参数 while (addressList.size() < targetSize) { addressList.add(addressList.get(originalSize - 1)); } // 后续正常传入namedParameterJdbcTemplate即可 SqlParameterSource namedParameters = new MapSqlParameterSource().addValue("addresses", addressList); return namedParameterJdbcTemplate.queryForObject("SELECT COUNT(*) FROM EMPLOYEE WHERE ADDRESS IN (:addresses)", namedParameters, Integer.class);
2. 自动转义后拼接SQL
使用Spring JDBC自带的SqlLiteralValue自动完成参数转义,再拼接成IN子句,既规避SQL注入风险,也不会生成多个预编译语句:
List<String> addressList = Arrays.asList("Canada", "USA", "Finland"); // 自动转义所有参数,避免注入 String inClause = addressList.stream() .map(addr -> new SqlLiteralValue(addr).toString()) .collect(Collectors.joining(",", "IN(", ")")); String sql = "SELECT COUNT(*) FROM EMPLOYEE WHERE ADDRESS " + inClause; // 直接执行拼接后的SQL即可 return jdbcTemplate.queryForObject(sql, Integer.class);
3. JSON参数传值(MySQL 5.7+支持)
将IN参数打包为单个JSON字符串作为预编译参数,用MySQL内置的JSON函数做匹配,SQL永远固定,只会生成1个预编译语句:
// 将地址列表序列化为JSON字符串 String jsonAddresses = new ObjectMapper().writeValueAsString(Arrays.asList("Canada", "USA", "Finland")); SqlParameterSource namedParameters = new MapSqlParameterSource().addValue("addresses", jsonAddresses); return namedParameterJdbcTemplate.queryForObject( "SELECT COUNT(*) FROM EMPLOYEE WHERE JSON_CONTAINS(:addresses, JSON_QUOTE(ADDRESS))", namedParameters, Integer.class );
内容的提问来源于stack exchange,提问作者Bojan Vukasovic
相关产品推荐
相关产品推荐

