【发布时间】:2017-09-20 14:19:17
【问题描述】:
我在 SQL Server 上存储了大文件,我需要将它们作为InputStreams 获取到流式传输到客户端而不会耗尽内存。我正在使用 Hibernate 5.2.11、WildFly 10.1 和 Microsoft JDBC Driver 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 刷新数据而不会耗尽内存?
【问题讨论】:
标签: java hibernate jpa spring-data-jpa mssql-jdbc