java 从 Spring REST 控制器返回流
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/39972903/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Returning a stream from a Spring REST Controller
提问by tzortzik
I am curios if it is possible to return a Stream
from a Spring RestController
如果可以Stream
从 Spring返回 a ,我是好奇心RestController
@RestController
public class X {
@RequestMapping(...)
public Stream<?> getAll() { ... }
}
Is it ok to do something like this? I tried and Spring returns something else other than the values of a stream.
做这样的事情可以吗?我试过,Spring 返回的不是流的值。
Shall I keep returning a List<?>
?
我要继续返回List<?>
吗?
回答by kinjelom
You can stream entities in Spring 5.0 / WebFlux.
您可以在Spring 5.0 / WebFlux中流式传输实体。
Take a look at this example REACTIVE Rest Controller(spring.main.web-application-type: "REACTIVE"
):
看看这个例子REACTIVE Rest Controller( spring.main.web-application-type: "REACTIVE"
):
@RestController
public class XService {
class XDto{
final int x;
public XDto(int x) {this.x = x;}
}
Stream<XDto> produceX(){
return IntStream.range(1,10).mapToObj(i -> {
System.out.println("produce "+i);
try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}
return new XDto(i);
});
}
// stream of Server-Sent Events (SSE)
@GetMapping(value = "/api/x/sse",
produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<XDto> getXSse() {
return Flux.fromStream(produceX());
}
// stream of JSON lines
@GetMapping(value = "/api/x/json-stream",
produces = MediaType.APPLICATION_STREAM_JSON_VALUE)
public Flux<XDto> getAllJsonStream() {
return Flux.fromStream(produceX());
}
// same as List<XDto> - blocking JSON list
@GetMapping(value = "/api/x/json-list",
produces = MediaType.APPLICATION_JSON_VALUE)
public Flux<XDto> getAll() {
return Flux.fromStream(produceX());
}
}
Spring Framework 5.0 - WebFlux:
Spring Framework 5.0 - WebFlux:
Spring's reactive stack web framework, new in 5.0, is fully reactive and non-blocking. It is suitable for event-loop style processing with a small number of threads.
Spring 的响应式堆栈 Web 框架是 5.0 中的新增功能,是完全响应式和非阻塞的。它适用于具有少量线程的事件循环样式处理。
Server-sent events is a standard describing how servers can initiate data transmission towards clients once an initial client connection has been established.
服务器发送事件是一个标准,描述了一旦建立了初始客户端连接,服务器如何向客户端发起数据传输。
回答by Jean Marois
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 Hymanson.
这也可以通过 Spring MVC Controller 来完成,但有一些问题:Spring Data JPA Repository 的限制,数据库是否支持 Holdable Cursors(ResultSet Holdability)和 Hymanson 的版本。
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.
我难以理解的关键概念是 Java 8 Stream 返回一系列在终端操作中执行的函数,因此必须在执行终端操作的上下文中访问数据库。
Spring Data JPA Limitations
Spring Data JPA 限制
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:
我发现 Spring Data JPA 文档没有为 Java 8 Streams 提供足够的细节。看起来您可以简单地声明Stream<MyObject> readAll()
,但我需要注释该方法@Query
以使其工作。我也无法使用 JPA 标准 API Specification
。所以我不得不接受一个硬编码的查询,比如:
@Query("select mo from MyObject mo where mo.foo.id in :fooIds")
Stream<MyObject> readAllByFooIn(@Param("fooIds") Long[] fooIds);
Holdable Cursor
可保持光标
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.
如果您有一个支持 Holdable Cursors 的数据库,则在提交事务后可以访问结果集。这很重要,因为我们通常用 注释我们的@Service
类方法@Transactional
,所以如果您的数据库支持可持有的游标,ResultSet
则可以在服务方法返回后访问,即在@Controller
方法中。如果数据库不支持可持有的游标,例如 MySQL,则需要将@Transaction
注释添加到控制器的@RequestMapping
方法中。
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):
所以现在 ResultSet 可以在@Service
方法之外访问,对吗?这又取决于可持有性。对于 MySQL,它只能在@Transactional
方法中访问,因此以下内容将起作用(尽管违背了使用 Java 8 Streams 的全部目的):
@Transaction @RequestMapping(...)
public List<MyObject> getAll() {
try(Stream<MyObject> stream = service.streamAll) {
return stream.collect(Collectors.toList())
};
}
but not
但不是
@Transaction @RequestMapping
public Stream<MyObject> getAll() {
return service.streamAll;
}
because the terminal operatoris notin your @Controller
it happens in Spring after the controller method returns.
因为码头运营商是不是在@Controller
它发生在Spring控制器方法返回后。
Serializing a stream to JSON without Holdable Cursor support
将流序列化为 JSON 而不支持 Holdable Cursor
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:
要将流序列化为没有可保持游标的 JSON,请添加HttpServletResponse response
到控制器方法,获取输出流并用于ObjectMapper
写入流。使用 FasterXML 3.x,您可以调用ObjectMapper().writeValue(writer, stream)
,但使用 2.8.x,您必须使用流的迭代器:
@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());
}
}
Next steps
下一步
My next steps are to attempt refactor this within a Callable
WebAsyncTask
and to move the JSON serialization into a service.
我接下来的步骤是尝试在 a 中重构它Callable
WebAsyncTask
并将 JSON 序列化移动到服务中。
References
参考
- 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
- 请务必阅读 Marko Topolnik 的博客文章https://www.airpair.com/java/posts/spring-streams-memory-efficiency,否则我不知道从哪里开始。
- MySQL > 5.0.2 现在支持游标,因此您可以添加
useCursorFetch=true
到连接字符串 -- https://dev.mysql.com/doc/connector-j/5.1/en/connector-j-reference-configuration-properties.html - Java 8 Stream 的 FasterXml 序列化 -- https://stackoverflow.com/a/37979665/2562746