如何将java.util.stream.Stream写入StreamingResponseBody输出流

时间:2020-04-26 14:07:07

标签: java spring-boot jpa-2.0

我正在构建一个REST API,可以通过流式传输到客户端应用程序(例如文件下载或直接流)将来自Oracle数据库的大量数据分块发送。

我正在从JpaRepository获取Stream,如下所示-

@Query("select u from UsersEntity u")
Stream<UsersEntity> findAllByCustomQueryAndStream();

但是现在面临的挑战是将此流写入StreamingResponseBody Output流

我尝试了很多方法但没有成功-

第一种方法-

Stream<UsersEntity> usersResultStream = usersRepository.findAllByCustomQueryAndStream();

        StreamingResponseBody stream = outputStream -> {
            Iterator<UsersEntity> iterator = usersResultStream.iterator();

            try (ObjectOutputStream oos = new ObjectOutputStream(outputStream)) {

                while (iterator.hasNext()) {
                    oos.write(iterator.next().toString().getBytes());
                }
            }
        };

出现错误-

java.sql.SQLException: Closed Resultset: next
    at oracle.jdbc.driver.InsensitiveScrollableResultSet.next(InsensitiveScrollableResultSet.java:565) ~[ojdbc7-12.1.0.2.jar:12.1.0.2.0]

第二种方法-

StreamingResponseBody stream = new StreamingResponseBody() {

            @Transactional(readOnly = true)
            @Override
            public void writeTo(OutputStream outputStream) throws IOException {

                Stream<UsersEntity> usersResultStream = usersRepository.findAllByCustomQueryAndStream();

                try (ObjectOutputStream oos = new ObjectOutputStream(outputStream)) {

                    usersResultStream.forEach(user->{
                        try {
                            oos.write(user.toString().getBytes());
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    });
                }
            }
        }; 

出现错误-

org.springframework.dao.InvalidDataAccessApiUsageException: You're trying to execute a streaming query method without a surrounding transaction that keeps the connection open so that the Stream can actually be consumed. Make sure the code consuming the stream uses @Transactional or any other way of declaring a (read-only) transaction.

我已经在下面给出的链接中上传了练习代码- Sample POC Link

我没有流媒体相关任务的任何经验,因此请帮助我。

如果我的方向不正确,则建议在 Spring Framework 中建议采用其他任何方法。请分享所有参考链接(如果有)。

2 个答案:

答案 0 :(得分:1)

没有示例显示StreamingResponseBody的“如此复杂”用法,并且我担心它“不可能”(至少我无法使用StreamingResponseBody Stream来管理/修复它)查询)

...但是,可能的是:

  1. 在StreamingResponseBody中使用findAll()(普通的非流式List-repo方法)。

    (但是我了解异步执行Web请求的“需求” ...和db请求“流式传输” ...)

  2. 使用Callable(异步网络请求)和@Async CompletableFuture<..>(异步数据库请求):

    @RestController
    @RequestMapping("/api")
    public class UsersController {
    
       @Autowired
       private UsersRepository usersRepository;
    
       @GetMapping(value = "/async/users")
       public Callable<List<UsersEntity>> fetchUsersAsync() {
           Callable callable = () -> {
               return usersRepository.readAllBy().get();
           };
           return callable;
       }
    }
    

    ..以及类似的存储库:

    @Repository
    public interface UsersRepository extends JpaRepository<UsersEntity, Integer> {
    
        @Async
        CompletableFuture<List<UsersEntity>> readAllBy();
    }
    

    (请参阅spring-samples) .. 不要忘记在您的应用程序/配置上@EnableAsync

    @org.springframework.scheduling.annotation.EnableAsync
    @SpringBootApplication
    public class Application { ... }
    

抱歉,它甚至不是答案,而是我的发现-来不及发表评论了。

可以通过多种方式来实现异步Web请求。 (请参见https://spring.io/blog/2012/05/10/spring-mvc-3-2-preview-making-a-controller-method-asynchronous/https://niels.nu/blog/2016/spring-async-rest.html,甚至没有提到“反应式” api)

答案 1 :(得分:1)

最后,我通过使用服务层解决了该问题。最初,我是在Controller类中编写导致问题的完整逻辑。

控制器类-

@RestController
@RequestMapping("/api")
public class UsersController {
    @Autowired
    private UserService service;

    @GetMapping(value = "/userstream")
    public ResponseEntity<StreamingResponseBody> fetchUsersStream() {

        StreamingResponseBody stream = this::writeTo;

        return new ResponseEntity<>(stream, HttpStatus.OK);
    }

    private void writeTo(OutputStream outputStream) {
        service.writeToOutputStream(outputStream);
    }
}

服务等级-

@Service
public class UserService {

    @Autowired
    private UsersRepository usersRepository;

    @Transactional(readOnly = true)
    public void writeToOutputStream(final OutputStream outputStream) {
        try (Stream<UsersEntity> usersResultStream = usersRepository.findAllByCustomQueryAndStream()) {
            try (ObjectOutputStream oos = new ObjectOutputStream(outputStream)) {

                usersResultStream.forEach(emp -> {
                    try {
                        oos.write(emp.toString().getBytes());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                });
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

完整代码位于github-https://github.com/bagesh2050/HttpResponseStreamingDemo

不过,我愿意提出与Http Streaming相关的建议。如果您有更好的主意,请提供。