queryForStream 返回的是需要关闭的资源入口。即使 findFirst 已经给出一个结果,调用方仍应让流经过明确的关闭路径。
前置:JDBC 与连接池。下面骨架未编译、未运行,只面向隔离测试数据源。
终止消费和关闭资源
queryForStream 返回需要调用方关闭的 Stream(流),官方建议使用 try-with-resources。源码把结果集、语句关闭及 DataSourceUtils.releaseConnection 放在流的 onClose 回调中。因此,findFirst 等终止操作取得结果,不应被当成已经完成资源释放;提前停止、消费时异常和正常耗尽,都应走明确的关闭路径。流对象也不宜直接交给模型工具或跨请求层长期持有。
最小资源作用域
假定 id 是非空主键,查询固定假数据,不拼接用户输入。
import java.util.Optional;
import java.util.stream.Stream;
Optional<Long> firstId;
try (Stream<Long> ids = jdbcTemplate.queryForStream(
"select id from merchant_test order by id",
(rs, rowNum) -> rs.getLong("id"))) {
firstId = ids.findFirst(); // 读取首项并不替代关闭
} // 正常返回或消费异常,均退出资源作用域
练习一 三种消费路径
在非事务、单线程、独占的隔离测试数据源中,用可记录关闭调用的 JDBC 包装器分别执行全量消费、findFirst 提前终止和 RowMapper 主动抛出测试异常三组。每组都使用 try-with-resources;断言结果集与语句的关闭调用,以及本组借出的连接被释放。另设漏写关闭的对照组观察差异,但必须在 finally 中显式补关,避免测试污染。不要仅凭返回条数或池级瞬时计数断言释放成功。
练习二 对照事务内外
对照无事务与由 Spring 管理的事务场景,记录流关闭、事务完成及连接可再次借用的先后顺序。连接释放服从事务管理,不把 releaseConnection 调用等同于立刻物理关闭连接或立即归还池;先核实实际事务管理器与数据源行为,再写断言。工具层只返回已消费并脱离 JDBC 资源的有界 DTO,而非活的 Stream。
驱动与资源边界
此处 Stream 是 Java 消费接口,不保证驱动一定使用服务器游标,也不证明内存占用恒定或数据库只扫描了返回行数。驱动版本、抓取配置、数据规模与项目兼容性需单独实测;不要求为了此练习迁移已有 MyBatis/JPA 项目。本节没有运行数据库测试。
来源与版本
Spring Framework 7.0.10。资料原核查时间为 2026 年 10 月 28 日 00:07–00:09 JST,独立发布日期未标明。来源:JdbcTemplate 查询流 API、固定标签实现。