Hibernate join multiple BLOBs

Viewed 156

I have multiple BLOBs that I want to concat to one another, in order to get one joined BLOB. In the process, the contents of the BLOBs may not be stored in memory.

My first idea was to merge the streams like that:

long size = blobs.get(0).length();
InputStream res = blobs.get(0).getBinaryStream();
for (int i = 1; i < blobs.size(); i++){
    res = Stream.concat(res, blobs.get(i).getBinaryStream());
    size += blobs.get(i).length();
}
Blob blob = Hibernate.getLobCreator(session).createBlob(res, size);

However, this obviously only works with Java 8 streams (not normal BinaryStreams) - and we use Java 7 anways.

My second idea then was to join the BLOBs by directly writing into its stream:

public Blob joinBlobsForHibernate(final Session session, final List<Blob> blobs) throws SQLException {
    final LobCreator lc = Hibernate.getLobCreator(session);
    final Blob resBlob = lc.createBlob(new byte[0]);

    try {
        OutputStream stream = resBlob.setBinaryStream(1);
        for (final Blob blob : blobs) {
            pipeInputStream(blob.getBinaryStream(), stream);
        }
        return resBlob;
    } catch (IOException | SQLException e){
        logger.error("Creating the blob threw an exception", e);
        return null;
    }
}

(pipeInputStream merely pipes the content of one stream into the other:

private void pipeInputStream (final InputStream is, final OutputStream os) throws IOException {
    final int buffSize = 128000;
            
    int n;
    final byte[] buff = new byte[buffSize];
    while ((n = is.read(buff)) >= 0){
        os.write(buff, 0, n);
}

)

This however yields in the following exception:

java.lang.UnsupportedOperationException: Blob may not be manipulated from creating session

And besides, I have the suspicion that the BLOB would still temporarily store the whole content in memory.


As a third try I tried using a custom InputStream:

/**
 * Combines multiple streams into one
 */
public class JoinedInputStream extends InputStream {

    private List<InputStream> parts;

    private List<InputStream> marked;

    public JoinedInputStream(final List<InputStream> parts) {
        this.parts = parts;
    }

    @Override
    public int read() throws IOException {

        int res = -1;
        while (res == -1 && parts.size() > 0) {
            try {
                if ((res = parts.get(0).read()) == -1) {
                    // The stream is done, so we won't try to read from it again
                    parts.remove(0);
                }
            } catch (IOException e) {
                e.printStackTrace();
                throw e;
            }
        }
        return res;
    }

    @Override
    public synchronized void reset() throws IOException {
        parts = new ArrayList<>(marked);
        if (parts.size() > 0) {
            parts.get(0).reset();
        }
    }

    @Override
    public synchronized void mark(final int readlimit) {
        marked = new ArrayList<>(parts);
        if (marked.size() > 0)
            marked.get(0).mark(readlimit);
    }

    @Override
    public boolean markSupported() {
        return true;
    }

    @Override
    public void close() throws IOException {
        super.close();
        for (final InputStream part : parts) {
            part.close();
        }
        parts = new ArrayList<>();
        marked = new ArrayList<>();
    }
}

The BLOB could then be joined like that (don't mind the unnecessary functions, they have other uses):

@Override
public Blob createBlobForHibernate(final Session session, final InputStream stream, final long length) {
    final LobCreator lc = Hibernate.getLobCreator(session);
    return lc.createBlob(stream, length);
}
@Override
public Blob createBlobForHibernate(final Session session, final List<InputStream> streams, final long length) {
    final InputStream joined = new JoinedInputStream(streams);
    return createBlobForHibernate(session, joined, length);
}
@Override
public Blob joinBlobsForHibernate(final Session session, final List<Blob> blobs) throws SQLException {

    long length = 0;
    List<InputStream> streams = new ArrayList<>(blobs.size());
    for (final Blob blob : blobs) {
        length += blob.length();
        streams.add(blob.getBinaryStream());
    }
    return createBlobForHibernate(session, streams, length);
}

However, this results in the following error (when persisting the newly created entity with the joined BLOB):

Caused by: java.sql.SQLException: LOB-Lese-/Schreibfunktionen aufgerufen, während ein anderer Lese-/Schreibvorgang ausgeführt wird: getBytes() at oracle.jdbc.driver.T4CConnection.getBytes(T4CConnection.java:3200) at oracle.sql.BLOB.getBytes(BLOB.java:391) at oracle.jdbc.driver.OracleBlobInputStream.needBytes(OracleBlobInputStream.java:166) ... 101 more

Or in English:

Lob read/write functions called while another read/write is in progress: getBytes()

I already tried setting hibernate.temp.use_jdbc_metadata_defaults to false (as suggested in this post) - in fact, we already had this property set beforehand and it didn't help.

0 Answers
Related