如何解决带有IN子句的Spring batchUpdate 解决方案
给出一个简单的表
create table car (
make varchar
model varchar
)
以及以下DAO代码
NamedParameterJdbcTemplate template;
String SQL = "delete from car where make = :make and model in (:model)";
void batchDelete(final Map<String,Collection<String>> map) {
SqlParameterSource[] params = map.entrySet().stream()
.map(entry -> toParams(entry.getKey(),entry.getValue()))
.toArray(SqlParameterSource[]::new);
template.batchUpdate(SQL,params);
}
void delete(final Map<String,Collection<String>> map) {
map.forEach((make,models) -> {
SqlParameterSource params = toParams(make,models);
template.update(SQL,params);
});
}
SqlParameterSource toParams(final String make,final Collection<String> models) {
return new MapSqlParameterSource("make",make)
.addValue("model",new ArrayList<>(models));
}
当映射具有2个键,且这些键的批处理中的IN子句具有不同数量的值时,批处理删除功能将失败。假设Map.of
创建并排序了地图。
// runs fine - 2 values for each key
batchDelete(Map.of("VW",Arrays.asList("Polo","Golf"),"Toyota",Arrays.asList("Yaris","Camry")));
// fails - first key has 1 value,second key has 2 values
batchDelete(Map.of("Toyota",Arrays.asList("Yaris"),"VW","Golf")));
// runs fine - key with bigger list comes first
batchDelete(Map.of("VW",Arrays.asList("Yaris")));
// non batch delete runs fine either way
delete(Map.of("Toyota","Golf")));
Spring文档暗示了这一点
https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#jdbc-in-clause
SQL标准允许基于包含变量值列表的表达式选择行。一个典型的示例是从T_ACTOR中选择*,其中id在(1、2、3)中。 JDBC标准不直接为准备好的语句支持此变量列表。您不能声明可变数量的占位符。您需要准备好所需数量的占位符的多种变体,或者一旦知道需要多少个占位符,就需要动态生成SQL字符串。在NamedParameterJdbcTemplate和JdbcTemplate中提供的命名参数支持采用后一种方法。
错误消息是
The column index is out of range: 3,number of columns: 2.; nested exception is org.postgresql.util.PSQLException: The column index is out of range: 3,number of columns: 2.
NamedParameterJdbcTemplate # batchUpdate
中的以下行会发生什么:
PreparedStatementCreatorFactory pscf = getPreparedStatementCreatorFactory(parsedSql,batchArgs[0]);
将在第一批arg长度之外创建一个动态sql:
delete from car where make = ? and model in (?)
因为只有1个占位符,所以具有2个模型的第二批项目将失败。
什么是解决方法? (而不是按值的数量对地图条目进行分组)
解决方案
回到简单的旧PreparedStatement
SQL-使用ANY
代替IN
delete from car where make = ? and model = any (?)
道
Connection con;
PreparedStatement ps = con.prepareStatement("SQL");
map.forEach((make,models) -> {
int col = 0;
ps.setString(++col,make);
ps.setArray(++col,con.createArrayOf("text",models));
ps.addBatch();
});
ps.executeBatch();
解决方法
我建议更改SQL,使其看起来像这样:
String SQL = "DELETE FROM car WHERE (make,model) IN (:ids)";
如果您这样做,则可以使用与我在此问题上给出的答案类似的方法:NamedJDBCTemplate Parameters is list of lists。这样做意味着您可以使用NamedParameterJdbcTemplate.update(String sql,Map<String,?> paramMap)
。在您的paramMap
中,键将是"ids"
,值将是Collection<Object[]>
的实例,其中集合中的每个条目都是一个包含要删除的值对的数组:>
List<Object[]> params = new ArrayList<>();//you can make this any instance of collection you want
for (Car car : cars) {
params.add(new Object[] { car.getMake(),car.getModel() });
//this is just to provide an example of what I mean,obviously this will probably be different in your app.
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。