使用Spring MVC流可关闭资源 [英] Stream closeable resource with Spring MVC

查看:134
本文介绍了使用Spring MVC流可关闭资源的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

阅读这篇文章后,我希望使用Spring将数据库查询结果直接流式传输到JSON响应,以确保常量内存使用(内存中没有贪婪地加载 List )。

After having read this article, I wish to use Spring to stream database query results directly to a JSON response to ensure constant-memory usage (no greedy loading of a List in memory).

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

Similar to what is done in the article with Hibernate, I assembled a greetingRepository object which returns a stream of the database contents based on a JdbcTemplate. In that implementation, I create an iterator over the queried ResultSet, and I return the stream as follows:

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

即。使用 onClose()方法保证如果在<$ c中声明流,将关闭基础 ResultSet $ c> try-with-resources construct:

i.e. with an onClose() method guaranteeing that the underlying ResultSet will be closed if the stream is declared in a try-with-resources construct:

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

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

But as in the article, I want this stream to be consumed by a custom object mapper (the enhanced MappingJackson2HttpMessageConverter defined in the article). If we take the try-with-resources need aside, this is feasible as follows:

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

然而,正如一位同事在该文章的底部发表评论,这不需要关闭底层资源。

However as a colleague commented at the bottom of that article, this does not take care of closing underlying resources.

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

In the context of Spring MVC, how can I stream from the database all the way into a JSON response and still guarantee that the ResultSet will be closed? Could you provide a concrete example solution?

推荐答案

您可以创建一个构造来推迟序列化时的查询执行。此构造将以programmaticaly方式开始和结束交易。

You could create a construct to defer the query execution at the serialization time. This construct will start and end the transaction programmaticaly.

public class TransactionalStreamable<T> {

    private final PlatformTransactionManager platformTransactionManager;

    private final Callable<Stream<T>> callable;

    public TransactionalStreamable(PlatformTransactionManager platformTransactionManager, Callable<Stream<T>> 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<T> c) {
        try (Stream<T> s = stream()){
            s.forEach(c);
        }
    }
}

使用专用的json序列化器:

Using a dedicated json serializer:

JsonSerializer<?> transactionalStreamableSer = new StdSerializer<TransactionalStreamable<?>>(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<GreetingResource> stream() {
  return new TransactionalStreamable(platformTransactionManager , () -> greetingRepository.stream().map(GreetingResource::new));
}

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

All the work will be done when jackson will serialize the object. It may be or not an issue regarding the error handling (eg. using controller advice).

这篇关于使用Spring MVC流可关闭资源的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆