从Spring REST控制器返回流 [英] Returning a stream from a Spring REST Controller

查看:807
本文介绍了从Spring REST控制器返回流的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如果可以从Spring RestController返回Stream

I am curios if it is possible to return a Stream from a Spring RestController

@RestController
public class X {
  @RequestMapping(...)
  public Stream<?> getAll() { ... }
}

可以这样做吗?我尝试过,Spring返回了流值以外的其他值.

Is it ok to do something like this? I tried and Spring returns something else other than the values of a stream.

我应该继续返回List<?>吗?

推荐答案

这也可以使用Spring MVC Controller来完成,但是有一些问题:Spring Data JPA Repository中的限制,数据库是否支持Holdable Cursors(ResultSet Holdability) )和杰克逊的版本.

This can also be accomplished with Spring MVC Controller, but there are a few concerns: limitations in Spring Data JPA Repository, whether the database supports Holdable Cursors (ResultSet Holdability) and the version of Jackson.

我很难理解的关键概念是Java 8 Stream返回了一系列在终端操作中执行的功能,因此必须在执行以下操作的上下文中访问数据库.终端操作.

The key concept, I struggled to appreciate, is that a Java 8 Stream returns a series of functions which execute in a terminal operation, and therefore the database has to be accessible in the context executing the terminal operation.

Spring Data JPA限制

我发现Spring Data JPA文档没有为Java 8 Streams提供足够的细节.看起来您可以简单地声明Stream<MyObject> readAll(),但是我需要用@Query注释该方法以使其起作用.我也无法使用JPA标准API Specification.因此,我必须解决像这样的硬编码查询:

I found the Spring Data JPA documentation does not provide enough detail for Java 8 Streams. It looks like you can simply declare Stream<MyObject> readAll(), but I needed to annotate the method with @Query to make it work. I was also not able to use a JPA criteria API Specification. So I had to settle for a hard-coded query like:

@Query("select mo from MyObject mo where mo.foo.id in :fooIds")
Stream<MyObject> readAllByFooIn(@Param("fooIds") Long[] fooIds);

可保持光标

如果您有一个支持Holdable Cursors的数据库,则在提交事务后可以访问结果集.这很重要,因为我们通常使用@Transactional注释@Service类方法,因此,如果您的数据库支持可保留的游标,则可以在服务方法返回后(即在@Controller方法中)访问ResultSet.如果数据库不支持可保留的游标,例如MySQL,您需要在控制器的@RequestMapping方法中添加@Transaction批注.

If you have a database supporting Holdable Cursors, the result set is accessible after the transaction is committed. This is important since we typically annotate our @Service class methods with @Transactional, so if your database supports holdable cursors the ResultSet can be accessed after the service method returns, i.e. in the @Controller method. If the database does not support holdable cursors, e.g. MySQL, you'll need to add the @Transaction annotation to the controller's @RequestMapping method.

因此,现在可以在@Service方法之外访问ResultSet,对吗?这又取决于可保持性.对于MySQL,只能在@Transactional方法中访问它,因此以下内容将起作用(尽管违反了使用Java 8 Streams的全部目的):

So now the ResultSet is accessible outside the @Service method, right? That again depends on holdability. For MySQL, it's only accessible within the @Transactional method, so the following will work (though defeats the whole purpose of using Java 8 Streams):

@Transaction @RequestMapping(...)
public List<MyObject> getAll() {
   try(Stream<MyObject> stream = service.streamAll) {
        return stream.collect(Collectors.toList())
    };
}

但不是

@Transaction @RequestMapping
public Stream<MyObject> getAll() {
    return service.streamAll;
}

因为终端操作符在您的@Controller中不是不是,它发生在控制器方法返回后的Spring中.

because the terminal operator is not in your @Controller it happens in Spring after the controller method returns.

在不支持可移动光标的情况下将流序列化为JSON

要在没有可保留游标的情况下将流序列化为JSON,请将HttpServletResponse response添加到控制器方法,获取输出流,然后使用ObjectMapper编写该流.使用FasterXML 3.x,您可以调用ObjectMapper().writeValue(writer, stream),但是使用2.8.x,则必须使用流的迭代器:

To serialize the stream to JSON without a holdable cursor, add HttpServletResponse response to the controller method, get the output stream and use ObjectMapper to write the stream. With FasterXML 3.x, you can call ObjectMapper().writeValue(writer, stream), but with 2.8.x you have to use the stream's iterator:

@RequestMapping(...)
@Transactional
public void getAll(HttpServletResponse response) throws IOException {
    try(final Stream<MyObject> stream = service.streamAll()) {
        final Writer writer = new BufferedWriter(new OutputStreamWriter(response.getOutputStream()));
        new ObjectMapper().writerFor(Iterator.class).writeValue(writer, stream.iterator());
    }
}

下一步

我的下一步是尝试在Callable WebAsyncTask内重构它,并将JSON序列化移到服务中.

My next steps are to attempt refactor this within a Callable WebAsyncTask and to move the JSON serialization into a service.

参考

  • Be sure to read Marko Topolnik's blog post https://www.airpair.com/java/posts/spring-streams-memory-efficiency, without which I wouldn't have known where to start.
  • MySQL >5.0.2 now supports cursors, so you can add useCursorFetch=true to the connection string -- https://dev.mysql.com/doc/connector-j/5.1/en/connector-j-reference-configuration-properties.html
  • FasterXml serialization of Java 8 Stream -- https://stackoverflow.com/a/37979665/2562746

这篇关于从Spring REST控制器返回流的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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