Spring 通过HTTP为PostgreSQL大型对象提供服务

Spring 通过HTTP为PostgreSQL大型对象提供服务,spring,postgresql,hibernate,spring-mvc,vaadin,Spring,Postgresql,Hibernate,Spring Mvc,Vaadin,我正在构建一个应用程序,通过RESTAPI(使用SpringMVC)和PWA(使用Vaadin)从PostgreSQL数据库提供数据服务 PostgreSQL数据库使用存储高达2GB的文件(我无法控制);JDBC驱动程序通过提供对二进制内容的流式访问,因此数据不需要完全读取到内存中 唯一的要求是blob中的流必须在同一事务中使用,否则JDBC驱动程序将抛出 问题是,即使我在事务存储库方法中检索流,Spring MVC和Vaadin的StreamResource都将在事务之外使用它,因此JDBC驱

我正在构建一个应用程序,通过RESTAPI(使用SpringMVC)和PWA(使用Vaadin)从PostgreSQL数据库提供数据服务

PostgreSQL数据库使用存储高达2GB的文件(我无法控制);JDBC驱动程序通过提供对二进制内容的流式访问,因此数据不需要完全读取到内存中

唯一的要求是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();
    }
}
对于这个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);
    }
}
除此之外:

org.postgresql.util.PSQLException: ERROR: invalid large-object descriptor: 0
这意味着在读取流时事务已经关闭

我认为有两种可能的解决办法:

  • 在下载过程中保持交易打开
  • 在事务期间将流写入磁盘,然后在下载期间从磁盘提供文件
  • 解决方案1是一种反模式和安全风险:事务持续时间由客户端决定,读卡器速度慢或攻击者可能会阻止数据访问

    解决方案2在客户端请求和服务器响应之间创建了巨大的延迟,因为流首先从数据库读取并写入磁盘

    一个想法可能是,在使用数据库中的数据写入文件时,开始从磁盘读取数据,以便传输立即开始,但事务持续时间将与客户端下载分离;但我不知道这会有什么副作用


    如何实现以安全、高效的方式为PostgreSQL大型对象提供服务的目标?

    一个选项是,如您所述,将读取与数据库分离,并将响应写入客户端。缺点是解决方案的复杂性,您需要在读写器之间进行同步

    另一种选择是首先在主事务中获取大对象id,然后在块中读取数据,每个块在单独的事务中读取

    byte[] getBlobChunk(Connection connection, long lobId, long start, long chunkSize) throws SQLException { 
       Blob blob = PgBlob(connection, lobId);
       InputStream is = blob.getBinaryStream(start, chunkSize);
       return IOUtils.toByteArray(is);
    }
    
    此解决方案简单得多,但有建立新连接的开销,如果您使用连接池,这应该不是什么大问题。

    我们在中通过使用线程+管道流和一个特殊的inputstream包装器
    ClosingInputStream
    来解决此问题,该包装器延迟关闭连接/事务,直到使用者关闭关闭输入流。也许类似的东西也能帮你

    仅供参考。我们发现,与类似的数据库相比,使用Postgres的OID和大型对象API的速度非常慢

    也许您也可以将SpringContentJPA改装到您的解决方案中,从而使用它的http端点(以及我刚才概述的解决方案),而不是创建自己的端点?类似这样的:

    pom.xml

    SomeEntityContentStore.java

    @StoreRestResource(path=“someEntityContent”)
    公共接口SomeEntityContentStore扩展ContentStore{
    }
    

    只需获取REST端点,即可将内容与实体关联
    SomeEntity
    。我们的示例回购协议中有一个工作示例。

    谢谢您的回答。我更喜欢在单个事务中读取流,因为在多个事务之间,流可能会改变。
    byte[] getBlobChunk(Connection connection, long lobId, long start, long chunkSize) throws SQLException { 
       Blob blob = PgBlob(connection, lobId);
       InputStream is = blob.getBinaryStream(start, chunkSize);
       return IOUtils.toByteArray(is);
    }
    
       <!-- 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>
    
    @Entity
    public class SomeEntity {
       @Id
       @GeneratedValue
       private long id;
    
       @ContentId
       private String contentId;
    
       @ContentLength
       private long contentLength = 0L;
    
       @MimeType
       private String mimeType = "text/plain";
    
       ...
    }
    
    @StoreRestResource(path="someEntityContent")
    public interface SomeEntityContentStore extends ContentStore<SomeEntity, String> {
    }