我正在构建一个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 中建议采用其他任何方法。请分享所有参考链接(如果有)。
答案 0 :(得分:1)
没有示例显示StreamingResponseBody
的“如此复杂”用法,并且我担心它“不可能”(至少我无法使用StreamingResponseBody 和 Stream来管理/修复它)查询)
...但是,可能的是:
在StreamingResponseBody中使用findAll()
(普通的非流式List-repo方法)。
(但是我了解异步执行Web请求的“需求” ...和db请求“流式传输” ...)
使用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相关的建议。如果您有更好的主意,请提供。