Spring Core 5.3.31的JdbcTemplate.batchUpdate()为何远慢于4.2.9.RELEASE?
Spring Core升级后NamedParameterJdbcTemplate.batchUpdate性能骤降的原因分析
核心原因:Spring JDBC批量处理逻辑的版本变更
1. 命名参数批量处理的实现逻辑差异
Spring 4.2.x中,调用NamedParameterJdbcTemplate.batchUpdate(String sql, Map<String, ?>[] batchValues)时,内部会将整个参数数组转换为原生JDBC批处理请求:预编译SQL语句后一次性绑定所有批处理参数,直接调用驱动的executeBatch()完成批量执行,充分利用了数据库的批处理优化能力。
而Spring 5.3.x对该方法的处理逻辑做了调整:针对Map[]类型的参数,默认会逐个遍历每个Map、单独完成参数绑定与执行,相当于把批量操作拆成了N次单条SQL执行,完全丧失了批处理的性能优势,这是耗时从4秒暴涨至3分钟的核心原因。
2. 参数绑定与SQL解析的额外开销
Spring 5.x对命名参数的解析、绑定逻辑进行了重构,单条操作的性能有所提升,但在批量场景下,重复的参数匹配、类型转换操作会累积大量额外开销。尤其当每个Map包含较多键值对时,这种重复处理的耗时会被显著放大。
3. 批处理模式的默认策略变更
Spring 5.x中,NamedParameterJdbcTemplate对Map[]参数的处理不再默认启用JDBC原生批处理,转而采用了兼容性更强但性能更低的逐行处理策略;而Spring 4.x则优先启用原生JDBC批处理,这直接导致了跨版本的性能差距。
验证与解决方向
- 验证JDBC批处理是否生效:开启Oracle JDBC驱动的DEBUG日志,查看日志中是否存在
executeBatch()调用记录。如果是多次executeUpdate(),则说明批处理未生效。 - 替换参数类型为SqlParameterSource[]:Spring 5.x对
SqlParameterSource数组的批量处理做了专门优化,能有效复用预编译SQL并启用原生批处理:private void runBatchInsert() { SqlParameterSource[] batchArgs = batchedRecords.stream() .map(MapSqlParameterSource::new) .toArray(SqlParameterSource[]::new); jdbcTemplate.batchUpdate(sql, batchArgs); } - 直接使用JdbcTemplate原生批处理方法:绕过NamedParameterJdbcTemplate的参数转换,手动实现
BatchPreparedStatementSetter确保原生批处理生效:@Autowired private JdbcTemplate jdbcTemplate; private void runBatchInsert() { jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { Map<String, ?> record = batchedRecords.get(i); // 手动设置对应参数,例如:ps.setString(1, (String) record.get("columnName")); } @Override public int getBatchSize() { return batchedRecords.size(); } }); }
内容的提问来源于stack exchange,提问作者Akash Gusain
相关产品推荐
相关产品推荐

