本文介绍了使用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流可关闭资源的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-11 04:16