簡體   English   中英

使用JPA / Hibernate從SQL Server流式傳輸二進制數據

[英]Streaming binary data from SQL Server with JPA/Hibernate

我在SQL Server上存儲了大文件,我需要將它們作為InputStream獲得,以流式傳輸到客戶端而不會耗盡內存。 我正在使用Hibernate 5.2.11,WildFly 10.1和Microsoft JDBC驅動程序6.2.1(支持到SQL Server的流傳輸)。

要將InputStream映射到我的實體,我需要創建一個自定義的Hibernate類型,因為不幸的是Hibernate沒有提供這種映射。

import java.io.InputStream;
import java.io.Serializable;
import java.sql.Blob;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Objects;

import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.BlobType;
import org.hibernate.type.SerializationException;
import org.hibernate.usertype.UserType;

public class InputStreamType implements UserType {

    private static final int[] SQL_TYPES = { Types.LONGVARBINARY };

    @Override
    public int[] sqlTypes() {
        return SQL_TYPES;
    }

    @Override
    public Class<?> returnedClass() {
        return InputStream.class;
    }

    @Override
    public boolean equals(Object x, Object y) throws HibernateException {
        return Objects.equals(x, y);
    }

    @Override
    public int hashCode(Object x) throws HibernateException {
        return Objects.hashCode(x);
    }

    @Override
    public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner)
            throws HibernateException, SQLException {
        Blob blob = (Blob) BlobType.INSTANCE.nullSafeGet(rs, names, session, owner);
        return blob == null ? null : blob.getBinaryStream();
    }

    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session)
            throws HibernateException, SQLException {
        if (value == null) {
            st.setNull(index, SQL_TYPES[0]);
        } else {
            st.setBinaryStream(index, (InputStream) value);
        }
    }

    @Override
    public Object deepCopy(Object value) throws HibernateException {
        return value;
    }

    @Override
    public boolean isMutable() {
        return false;
    }

    @Override
    public Serializable disassemble(Object value) throws HibernateException {
        throw new SerializationException("Cannot serialize " + InputStream.class.getName(), null);
    }

    @Override
    public Object assemble(Serializable cached, Object owner) throws HibernateException {
        return cached;
    }

    @Override
    public Object replace(Object original, Object target, Object owner) throws HibernateException {
        return original;
    }

}

然后,我注釋我的實體字段以使用此類型:

@Entity
public class User {

    @Id
    Long id;

    @Lob
    @Type(type = "InputStreamType")
    InputStream picture;

    // ...

    InputStream getPicture() {
        return this.picture;
    }

    // ...

}

但是當我嘗試讀取流時,出現異常:

@Service
public class UserService {

    @PersistenceContext
    EntityManager em;

    @Transactional
    public void testReadInputStream() {
        InputStream picture = em.find(User.class, 1L).getPicture();
        System.out.println(picture.getClass().getName());
        // prints: com.microsoft.sqlserver.jdbc.PLPInputStream
        picture.read();
        // throws: IOException The TDS protocol stream is not valid.
        //         at com.microsoft.sqlserver.jdbc.PLPInputStream.readBytes(PLPInputStream.java:304)
        //         at com.microsoft.sqlserver.jdbc.PLPInputStream.read(PLPInputStream.java:244)
    }

}

我試圖讀取InputStreamType.nullSafeGet的流,並且那里沒有拋出任何異常。

那么,從InputStreamType.nullSafeGet返回后的流將如何處理? 我如何仍然可以使用它?

更新1

我將情況簡化為:

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class Main {

    public static void main(String[] args) throws Exception {
        Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
        Connection connection = DriverManager.getConnection(
                "jdbc:sqlserver://localhost:1433;DatabaseName=test");
        Statement statement = connection.createStatement();
        ResultSet resultSet = statement.executeQuery("select picture from [User] where id = 1");
        resultSet.next();
        InputStream inputStream = resultSet.getBlob(1).getBinaryStream();
        System.out.println(inputStream.getClass().getName());
        // prints: com.microsoft.sqlserver.jdbc.PLPInputStream

        inputStream.read();
        // no exception

        statement.close();
        inputStream.read();
        // throws: IOException: The TDS protocol stream is not valid.
    }

}

CustomType返回的語句可能是關閉的,並且流變得不可訪問嗎? 如果是這樣,如何在JPA中克服呢?

更新2

我的最后發現:如果我返回Blob的流,則在會話關閉時它將關閉。 但是,如果我返回Blob本身,則在會話關閉時,流會以某種方式加載到內存中,從而耗盡大型流(我認為此行為是由Blob上的Hibernate代理觸發的)。

從存儲庫方法返回時,是否有辦法讓會話打開,以使流可通過HTTP訪問並刷新數據而不會耗盡內存?

如果我沒記錯的話,SQL設計根本就沒有數據流。 要么所有數據都已返回到SQL客戶端,要么根本沒有。 這是一件原子的事情。

您可能能夠做的就是將二進制數據拆分為多個較小的塊,然后將它們一個接一個地拉。 然后,可以在將每個數據塊發送到HTTP客戶端時關閉與數據庫的網絡連接。

或者,您可以將二進制數據作為文件存儲在文件系統中,然后從那里進行流傳輸。 但是,這意味着您必須自己注意文件系統和數據庫之間的一致性。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM