我正在构建一个应用程序,通过 REST API(使用 Spring MVC)和 PWA(使用 Vaadin)提供来自 PostgreSQL 数据库的数据。
PostgreSQL 数据库使用 Large Objects 存储最大 2GB 的文件(我无法控制); JDBC 驱动程序通过 Blob#getBinaryStream
提供对其二进制内容的流式访问,因此数据不需要完全读入内存。
唯一的要求是必须在同一事务中使用来自 blob 的流,否则 JDBC 驱动程序将抛出。
问题是,即使我在事务存储库方法中检索流,Spring MVC 和 Vaadin 的 StreamResource
都会在事务之外使用它,因此 JDBC 驱动程序会抛出错误。
例如给定
public interface SomeRepository extends JpaRepository<SomeEntity, Long> {
@Transactional(readOnly = true)
default InputStream getStream() {
return findById(1).getBlob().getBinaryStream();
}
}
这个 Spring MVC 方法会失败
@RestController
public class SomeController {
private final SomeRepository repository;
@GetMapping
public ResponseEntity getStream() {
var stream = repository.getStream();
var resource = new InputStreamResource(stream);
return new ResponseEntity(resource, HttpStatus.OK);
}
}
对于这个 Vaadin StreamResource
public class SomeView extends VerticalLayout {
public SomeView(SomeRepository repository) {
var resource = new StreamResource("x", repository::getStream);
var anchor = new Anchor(resource, "Download");
add(anchor);
}
}
同样的异常(exception):
org.postgresql.util.PSQLException: ERROR: invalid large-object descriptor: 0
这意味着在读取流时事务已经关闭。
我看到了两种可能的解决方案:
- 在下载过程中保持事务打开;
- 在交易期间将流写入磁盘,然后在下载期间从磁盘提供文件。
解决方案 1 是一种反模式和安全风险:事务持续时间由客户端决定,读取速度慢或攻击者都可能阻止数据访问。
解决方案 2 在客户端请求和服务器响应之间造成巨大的延迟,因为流首先从数据库中读取并写入磁盘。
一个想法可能是在用数据库中的数据写入文件时开始从磁盘读取数据,这样传输会立即开始,但事务持续时间将与客户端下载分离;但我不知道这可能有哪些副作用。
我如何才能以安全和高性能的方式实现为 PostgreSQL 大对象提供服务的目标?
最佳答案
我们在 Spring Content 中解决了这个问题通过使用线程 + 管道流和一个特殊的输入流包装器 ClosingInputStream
延迟关闭连接/事务,直到消费者关闭输入流。也许像 this也会帮助你吗?
仅供引用。我们发现与类似的数据库相比,使用 Postgres 的 OID 和大对象 API 的速度极慢。
也许您也可以将 Spring Content JPA 改造为您的解决方案,从而使用它的 http 端点(以及我刚刚概述的解决方案)而不是创建您自己的?像这样的东西:-
pom.xml
<!-- Java API -->
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-jpa-boot-starter</artifactId>
<version>0.4.0</version>
</dependency>
<!-- REST API -->
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-rest-boot-starter</artifactId>
<version>0.4.0</version>
</dependency>
SomeEntity.java
@Entity
public class SomeEntity {
@Id
@GeneratedValue
private long id;
@ContentId
private String contentId;
@ContentLength
private long contentLength = 0L;
@MimeType
private String mimeType = "text/plain";
...
}
SomeEntityContentStore.java
@StoreRestResource(path="someEntityContent")
public interface SomeEntityContentStore extends ContentStore<SomeEntity, String> {
}
您只需要获取 REST 端点,即可将内容与您的实体 SomeEntity
相关联。我们的示例存储库中有一个工作示例 here .
关于spring - 通过 HTTP 服务 PostgreSQL 大对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52669743/