This can also be done using the Spring MVC Controller, but there are a few problems: limitations in the Spring Data JPA Repository, whether the database supports Holdable Cursors (ResultSet Holdability) and the Jackson version.
The key concept with which I was trying to understand is that Java 8 Stream returns a number of functions that are executed in a terminal operation, and therefore the database must be accessible in the context of the terminal operation.
Spring JPA Data Limitations
I found that the Spring Data JPA documentation does not provide enough details for Java 8. It looks like you can just declare Stream<MyObject> readAll() , but I needed to annotate the method with @Query to make it work. I was also unable to use the JPA Specification API criteria. So I had to agree to a hard-coded request, for example:
@Query("select mo from MyObject mo where mo.foo.id in :fooIds") Stream<MyObject> readAllByFooIn(@Param("fooIds") Long[] fooIds);
Hold cursor
If you have a database that supports Holdable Cursors, the result set is available after the transaction. This is important because we usually comment on our methods of the @Service class using @Transactional , so if your database supports portable cursors, you can access the ResultSet after returning the service method, i.e. To the @Controller method. If the database does not support portable cursors, for example. MySQL, you need to add the @Transaction annotation to the @RequestMapping controller @RequestMapping .
So now ResultSet is available outside the @Service method, right? It again depends on sustainability. For MySQL, it is available only in the @Transactional method, so the following will work (although it defeats the whole purpose of using Java 8 threads):
@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; }
since the terminal statement is not in @Controller , this happens in Spring after the controller method returns.
Serializing a stream for JSON without supporting a supported cursor
To serialize a stream in JSON without a held cursor, add an 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 need to use a stream 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()); } }
Next steps
My next steps are to try refactoring in Callable WebAsyncTask and moving the JSON serialization to the service.
References