Spring JdbcTemplate+RowMapper查询少量数据过慢问题求助
看起来你遇到了一个头疼的问题:明明数据库直接执行SQL只需要不到1秒,用JdbcTemplate+BeanPropertyRowMapper跑却要11秒,而且结果只有12条数据。结合你的代码和技术栈,我来帮你分析下核心原因和解决办法:
核心问题分析
直接在数据库快、代码里慢,大概率不是SQL本身的问题,而是代码中SQL参数传递的方式错误,导致PostgreSQL无法使用索引,被迫全表扫描100万条数据;另外PostgreSQL JDBC驱动对fetchSize的特殊要求也可能是一个影响因素。
具体解决方案
1. 修复SQL参数拼接错误(最关键)
你当前代码里直接用字符串模板把groups拼进SQL:where group_id = any(ARRAY $groups),这种做法不仅有SQL注入风险,还会导致PostgreSQL无法正确识别数组参数,进而无法使用group_id字段的索引,只能全表扫描所有100万条数据,这才是性能慢的核心原因。
改成用JdbcTemplate的参数绑定功能,正确传递数组参数:
override fun getLineChartByGroupYear(userId: Long, groups: List<Long>): List<lineChartDTO>? { val sql = """ SELECT date, CASE WHEN TOTAL = 0 THEN 0 ELSE 1.0 * compliant / total end as compliance_rate from ( select date_trunc('month',location_timestamp) as date, count(case when event_type = 'COMPL_HANDWASH' then 1 else null end) as compliant, count(1) as total from compliance where group_id = any(?) and location_timestamp between (now() - interval '1 year') and now() group by 1 ) A ORDER BY date; """ val stopwatch = StopWatch() stopwatch.start() // 正确传递PostgreSQL数组参数 val res = jdbc.query( sql, arrayOf(jdbc.dataSource.connection.createArrayOf("bigint", groups.toTypedArray())), BeanPropertyRowMapper(lineChartDTO::class.java) ) stopwatch.stop() logger.info("LineChartByGroupYear query BeanPropertyRowMapper1: ${stopwatch.totalTimeSeconds}") return res }
2. 正确设置fetchSize(适配PostgreSQL驱动特性)
PostgreSQL JDBC驱动的fetchSize默认是0,这会一次性获取所有结果;而且只有在事务上下文中设置非零的fetchSize,驱动才会用游标分批取数据。虽然你的方法加了@Transactional,但还是建议显式配置:
方式一:全局配置JdbcTemplate的fetchSize
在你的DataSourceConfig里添加JdbcTemplate的Bean配置:
@Bean open fun jdbcTemplate(dataSource: DataSource): JdbcTemplate { val jdbcTemplate = JdbcTemplate(dataSource) jdbcTemplate.fetchSize = 50 // 结果集只有12条,50完全足够 return jdbcTemplate }
方式二:查询时手动设置fetchSize
如果不想全局配置,也可以在查询时通过PreparedStatementSetter设置:
val res = jdbc.query( sql, PreparedStatementSetter { ps -> val array = jdbc.dataSource.connection.createArrayOf("bigint", groups.toTypedArray()) ps.setArray(1, array) ps.fetchSize = 50 }, BeanPropertyRowMapper(lineChartDTO::class.java) )
3. 优化RowMapper(可选,进一步提升性能)
BeanPropertyRowMapper依赖反射赋值,虽然12条数据的开销不大,但如果想极致优化,可以自定义RowMapper避免反射:
// 自定义RowMapper val customRowMapper = RowMapper<lineChartDTO> { rs, _ -> val dto = lineChartDTO() dto.date = rs.getObject("date", LocalDateTime::class.java) dto.complianceRate = rs.getFloat("compliance_rate") dto } // 使用自定义RowMapper查询 val res = jdbc.query( sql, arrayOf(jdbc.dataSource.connection.createArrayOf("bigint", groups.toTypedArray())), customRowMapper )
4. 检查事务影响(可选)
你的服务层方法加了@Transactional,但这个查询是只读操作,你可以暂时去掉@Transactional测试下性能,看是否有事务相关的额外开销。
验证步骤
- 先修复参数绑定问题,这是最可能的根源,修复后执行查询,看耗时是否降到和数据库客户端一致
- 如果还有小幅度差异,再设置fetchSize
- 最后可以尝试自定义RowMapper进一步优化
内容的提问来源于stack exchange,提问作者Tal Shani

