从Spring REST控制器返回流 [英] Returning a stream from a Spring REST Controller
问题描述
如果可以从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.
参考
- 请务必阅读Marko Topolnik的博客文章 https://www. airpair.com/java/posts/spring-streams-memory-efficiency ,如果没有它,我将不知道从哪里开始.
- MySQL> 5.0.2现在支持游标,因此您可以将
useCursorFetch=true
添加到连接字符串- https://stackoverflow.com/a/37979665/2562746
- 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屋!