从Hibernate中的java.sql.Blob获取流

时间:2011-02-18 15:21:38

标签: database hibernate jpa blob

我正在尝试使用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的字节吗?我能做些什么让它发挥作用?

5 个答案:

答案 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方法是否关闭了输入流?