使用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的问题(例如,使用控制器建议)。