我正在尝试使用hibernate @Entity和java.sql.Blob来存储一些二进制数据。存储不会抛出任何异常(但是,我不确定它是否真的存储了字节),但是读取确实存在。这是我的测试:
@Test
public void shouldStoreBlob() {
InputStream readFile = getClass().getResourceAsStream("myfile");
Blob blob = dao.createBlob(readFile, readFile.available());
Ent ent = new Ent();
ent.setBlob(blob);
em.persist(ent);
long id = ent.getId();
Ent fromDb = em.find(Ent.class, id);
//Exception is thrown from getBinaryStream()
byte[] fromDbBytes = IOUtils.toByteArray(fromDb.getBlob().getBinaryStream());
}
所以它抛出异常:
java.sql.SQLException: could not reset reader
at org.hibernate.engine.jdbc.BlobProxy.getStream(BlobProxy.java:86)
at org.hibernate.engine.jdbc.BlobProxy.invoke(BlobProxy.java:108)
at $Proxy81.getBinaryStream(Unknown Source)
...
为什么呢?它不应该在这里读取DB的字节吗?我能做些什么让它发挥作用?
答案 0 :(得分:5)
尝试刷新实体:
em.refresh(fromDb);
将重新开启流。我怀疑find(...)正在关闭blob流。
答案 1 :(得分:2)
目前还不清楚如何使用JPA,但如果您使用的是JPA,则无需直接处理Blob数据类型。
你只需要在@Lob的实体中声明一个字段,如下所示:
@Lob
@Basic(fetch = LAZY)
@Column(name = "image")
private byte[] image;
然后,当您检索实体时,字段将在字段中再次回读,您将能够将它们放入流中并随意执行任何操作。
当然,您需要在实体中使用getter和setter方法来进行字节转换。在上面的例子中,它有点像:
private Image getImage() {
Image result = null;
if (this.image != null && this.image.length > 0) {
result = new ImageIcon(this.image).getImage();
}
return result;
}
这个二人有点像这样
private void setImage(Image source) {
BufferedImage buffered = new BufferedImage(source.getWidth(null), source.getHeight(null), BufferedImage.TYPE_INT_RGB);
Graphics2D g = buffered.createGraphics();
g.drawImage(source, 0, 0, null);
g.dispose();
ByteArrayOutputStream stream = new ByteArrayOutputStream();
try {
ImageIO.write(buffered, "JPEG", stream);
this.image = stream.toByteArray();
}
catch (IOException e) {
assert (false); // should never happen
}
}
}
答案 2 :(得分:1)
您需要在 stream.reset()行上的方法org.hibernate.engine.jdbc.BlobProxy#getStream上设置断点,并检查IOException的原因:
private InputStream getStream() throws SQLException {
try {
if (needsReset) {
stream.reset(); // <---- Set breakpoint here
}
}
catch ( IOException ioe) {
throw new SQLException("could not reset reader");
}
needsReset = true;
return stream;
}
在我的情况下,IOException的原因是使用 org.apache.commons.io.input.AutoCloseInputStream 作为Blob的来源:
InputStream content = new AutoCloseInputStream(stream);
...
Ent ent = new Ent();
...
Blob blob = Hibernate.getLobCreator(getSession()).createBlob(content, file.getFileSize())
ent.setBlob(blob);
em.persist(ent);
在刷新Session hibernate时关闭Inpustream 内容(或者更确切地说org.postgresql.jdbc2.AbstractJdbc2Statement #setBlob在我的情况下关闭Inpustream)。当AutoCloseInputStream关闭时 - 它会在方法 reset()
中引发IOException<强>更新强> 在您的情况下,您使用FileInputStream - 此流也会在重置方法上引发异常。 测试用例存在问题。您创建blob并在一个事务内从数据库中读取它。当您创建Ent时,Postgres jdbc驱动程序在刷新会话时关闭InputStream。当您加载Ent( em.find(Ent.class,id))时 - 您将获得相同的BlobProxy对象,该对象存储已关闭的InputStream。
试试这个:
TransactionTemplate tt;
@Test
public void shouldStoreBlob() {
final long id = tt.execute(new TransactionCallback<long>()
{
@Override
public long doInTransaction(TransactionStatus status)
{
try
{
InputStream readFile = getClass().getResourceAsStream("myfile");
Blob blob = dao.createBlob(readFile, readFile.available());
Ent ent = new Ent();
ent.setBlob(blob);
em.persist(ent);
return ent.getId();
}
catch (Exception e)
{
return 0;
}
}
});
byte[] fromStorage = tt.execute(new TransactionCallback<byte[]>()
{
@Override
public byte[] doInTransaction(TransactionStatus status)
{
Ent fromDb = em.find(Ent.class, id);
try
{
return IOUtils.toByteArray(fromDb.getBlob().getBinaryStream());
}
catch (IOException e)
{
return new byte[] {};
}
}
});
}
答案 3 :(得分:0)
我目前唯一的解决方案是关闭写会话并打开新的Hibernate会话以获取流数据。有用。但是我不知道有什么区别。我打电话给inputStream.close()
,但这还不够。
另一种方式:
我试图在free()
调用之后调用session.save(attachment)
blob方法,但它会抛出另一个异常:
Exception in thread "main" java.lang.AbstractMethodError: org.hibernate.lob.SerializableBlob.free()V
at my.hibernatetest.HibernateTestBLOB.storeStreamInDatabase(HibernateTestBLOB.java:142)
at my.hibernatetest.HibernateTestBLOB.main(HibernateTestBLOB.java:60)
我正在使用PostgreSQL 8.4 + postgresql-8.4-702.jdbc4.jar, Hibernate 3.3.1.GA
答案 4 :(得分:-1)
IOUtils.toByteArray方法是否关闭了输入流?