使用Spring MVC流可关闭资源

阅读完本文之后 ,我希望使用Spring将数据库查询结果直接流式传输到JSON响应,以确保内存使用率不变(内存中没有贪婪的List加载)。

与使用Hibernate的文章中所做的类似,我组装了一个greetingRepository对象,该对象根据JdbcTemplate返回数据库内容的流。 在该实现中,我在查询的ResultSet创建了一个迭代器,并按如下方式返回流:

 return StreamSupport.stream(spliterator(), false).onClose(() -> { log.info("Closing ResultSetIterator stream"); JdbcUtils.closeResultSet(resultSet); }); 

即使用onClose()方法,如果在try-with-resources构造中声明了流,则保证将关闭基础ResultSet

 try(Stream stream = greetingRepository.stream()) { // operate on the stream } // ResultSet underlying the stream will be guaranteed to be closed 

但正如在文章中,我希望这个流由自定义对象映射器(文章中定义的增强的MappingJackson2HttpMessageConverter )使用。 如果我们将try-with-resources需求放在一边,这可行如下:

 @RequestMapping(method = GET) Stream stream() { return greetingRepository.stream().map(GreetingResource::new); } 

然而,正如一位同事在该文章底部评论的那样,这并没有关闭基础资源。

在Spring MVC的上下文中,我如何从数据库一直流式传输到JSON响应中并仍然保证ResultSet将被关闭? 你能提供具体的示例解决方案吗?

您可以创建一个构造以在序列化时延迟查询执行。 此构造将以programmaticaly方式开始和结束事务。

 public class TransactionalStreamable { private final PlatformTransactionManager platformTransactionManager; private final Callable> callable; public TransactionalStreamable(PlatformTransactionManager platformTransactionManager, Callable> callable) { this.platformTransactionManager = platformTransactionManager; this.callable = callable; } public Stream stream() { TransactionTemplate txTemplate = new TransactionTemplate(platformTransactionManager); txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); txTemplate.setReadOnly(true); TransactionStatus transaction = platformTransactionManager.getTransaction(txTemplate); try { return callable.call().onClose(() -> { platformTransactionManager.commit(transaction); }); } catch (Exception e) { platformTransactionManager.rollback(transaction); throw new RuntimeException(e); } } public void forEach(Consumer c) { try (Stream s = stream()){ s.forEach(c); } } } 

使用专用的json序列化器:

 JsonSerializer transactionalStreamableSer = new StdSerializer>(TransactionalStreamable.class, true) { @Override public void serialize(TransactionalStreamable streamable, JsonGenerator jgen, SerializerProvider provider) throws IOException { jgen.writeStartArray(); streamable.forEach((CheckedConsumer) e -> { provider.findValueSerializer(e.getClass(), null).serialize(e, jgen, provider); }); jgen.writeEndArray(); } }; 

可以这样使用:

 @RequestMapping(method = GET) TransactionalStreamable stream() { return new TransactionalStreamable(platformTransactionManager , () -> greetingRepository.stream().map(GreetingResource::new)); } 

当jackson将序列化对象时,所有工作都将完成。 它可能是或者不是关于error handling的问题(例如,使用控制器建议)。