从Spring REST控制器返回流

tzo*_*zik 13 java spring

如果有可能Stream从春天回来,我很好奇RestController

@RestController
public class X {
  @RequestMapping(...)
  public Stream<?> getAll() { ... }
}
Run Code Online (Sandbox Code Playgroud)

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

我要一直回来List<?>吗?

Jea*_*ois 9

这也可以使用Spring MVC控制器来完成,但也有一些顾虑:限制在Spring JPA的数据仓库,数据库是否支持副手光标(ResultSet的可保存性)和杰克逊的版本。

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

Spring Data JPA限制

我发现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);
Run Code Online (Sandbox Code Playgroud)

固定光标

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

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

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

但不是

@Transaction @RequestMapping
public Stream<MyObject> getAll() {
    return service.streamAll;
}
Run Code Online (Sandbox Code Playgroud)

因为码头运营商不是@Controller它发生在Spring控制器方法返回后。

不支持Holdable Cursor的情况下将流序列化为JSON

要在没有可保留游标的情况下将流序列化为JSON,请添加HttpServletResponse response到controller方法中,获取输出流并用于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());
    }
}
Run Code Online (Sandbox Code Playgroud)

下一步

我的下一个步骤是尝试在a内进行重构Callable WebAsyncTask,并将JSON序列化移至服务中。

参考文献


wil*_*oop 8

您可以在Spring 5.0/WebFlux中流式传输实体.

看一下这个示例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());
    }
}
Run Code Online (Sandbox Code Playgroud)

Spring Framework 5.0 - WebFlux:

Spring的反应式堆栈Web框架是5.0中的新增功能,具有完全的反应性和非阻塞性.它适用于具有少量线程的事件循环样式处理.

服务器发送事件(SSE):

服务器发送的事件是描述一旦建立初始客户端连接后服务器如何向客户端发起数据传输的标准.

WebSockets与服务器发送的事件/事件源