Search code examples
javajdbcblobojdbc

blob.getBinaryStream independently closes


I have a java method which is fetching blob from oracle database (I'm using ojdbc8.jar). This method writes blob to OutputStream (for client):

// OutputStream os = response.getOutputStream();  // earlier
// Blob blob = resultSet.getBlob("data_binary");  // earlier

    int length;
    int bufSize = 4096;
    byte buffer[] = new byte[bufSize];
    try (InputStream is = blob.getBinaryStream()) {
        while ((length = is.read(buffer, 0, bufSize)) != -1) {
            out.write(buffer, 0, length);
            out.flush();
        }
        out.flush();
    }

and returns InputStream (for cache):

return blob.getBinaryStream();

And when I'm trying (on the other class) to write this InputStream to the disk:

    try (OutputStream os = new FileOutputStream(fileOnTheDisk)) {
        IOUtils.copy(blobIS, os);
    } catch (IOException e) {
        e.printStackTrace();
    }

I'm gettting Exceptions:

java.io.IOException: Closed Connection
at oracle.jdbc.driver.OracleBlobInputStream.needBytes(OracleBlobInputStream.java:204)
at oracle.jdbc.driver.OracleBufferedStream.readInternal(OracleBufferedStream.java:169)
at oracle.jdbc.driver.OracleBufferedStream.read(OracleBufferedStream.java:143)
at oracle.jdbc.driver.OracleBufferedStream.read(OracleBufferedStream.java:132)
at org.apache.commons.io.IOUtils.copyLarge(IOUtils.java:1025)
at org.apache.commons.io.IOUtils.copy(IOUtils.java:999)
at ru.miit.databasereader.OracleDatabaseReader.getbo(OracleDatabaseReader.java:262)
at ru.miit.contentservlet.ContentGetter.getObject(ContentGetter.java:85)
at ru.miit.contentservlet.ContentServlet.doGet(ContentServlet.java:131)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:137)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:651)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:409)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:754)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1376)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Unknown Source)
Caused by: java.sql.SQLRecoverableException: Closed Connection
at oracle.sql.BLOB.getDBAccess(BLOB.java:1122)
at oracle.sql.BLOB.getBytes(BLOB.java:348)
at oracle.jdbc.driver.OracleBlobInputStream.needBytes(OracleBlobInputStream.java:181)
... 31 more

I need a solution how to write this blob.getBinaryStream() to disk on the other class and to know what's the problem.

I tried to write this blob stream to disk in this (fetching) method and I didn't get an exceptins. And only when I return this stream to the other class, I have an exceptions.

UPDATED

Full code. DiskCache:

public class DiskCache {

public void put(final String location, final String name, final InputStream blobIS, OutpuStream servletOS) {

    DatabaseReader databaseReader = new DatabaseReader ();

    BinaryObject binaryobject = databaseReader.getBinaryDataByImageId(imageId, servletOS);

    File fileOnTheDisk = new File(location + name);

    try (OutputStream os = new FileOutputStream(fileOnTheDisk)) {
        IOUtils.copy(binaryobject.getIS(), os);
    } catch (IOException e) {
        e.printStackTrace();
    }

}

DatabaseReader:

public class DatabaseReader{

public Connection getConnection() throws NamingException, SQLException {

    Context initialContext = null;
    try {
        initialContext = new InitialContext();
        DataSource dataSource = (DataSource) initialContext.lookup(DATASOURCE_NAME);
        OracleConnection connection = (OracleConnection) dataSource.getConnection().unwrap(OracleConnection.class);
        return connection;
    } finally {
        if (initialContext != null){
            initialContext.close();
        }
    }

}
    public BinaryObject getBinaryDataByImageId(final String imageId, OutputStream osServlet) throws SQLException, NamingException, IOException{

     String sqlQuery = "***";

     BinaryObject binaryObject = null;
     try (Connection connection = getConnection();
            OraclePreparedStatement preparedStatement = (OraclePreparedStatement) connection
                    .prepareStatement(sqlQuery)) {
        try (ResultSet resultSet = preparedStatement.executeQuery()) {

            resultSet.next();
            Blob blob = resultSet.getBlob("blob_data");
            writeToStream(blob, osServlet); 
            binaryObject = new BinaryObject(imageId, blob.getBinaryStream());

        }
      }
      return binaryObject;
    }


    public void writeToStream(Blob blobData, OutputStream out) throws IOException, SQLException {

    int length;
    int bufSize = 4096;
    byte buffer[] = new byte[bufSize];
    try (InputStream is = blobData.getBinaryStream()) {
        while ((length = is.read(buffer, 0, bufSize)) != -1) {
            out.write(buffer, 0, length);
            out.flush();
        }
        out.flush();

    }

}
}

JNDI:

    <Resource name="jdbc/database" auth="Container"
    type="javax.sql.DataSource" maxActive="100" 
    maxIdle="30" maxWait="1000"
    username="***" 
    password="***"
    driverClassName="oracle.jdbc.OracleDriver"
    url="jdbc:oracle:thin***"/>

Solution

  • A blob should be read only once, within every ResultSet.next step. After the result set is closed (and it should be closed), and after the blob's input stream is read (closing the stream too) you are out of luck.

    If you want to serve the client fast, and store a duplicate in a cache you would need to do both at the same time:

    // OutputStream os = response.getOutputStream();  // earlier
    // Blob blob = resultSet.getBlob("data_binary");  // earlier
    // OutputStream cacheOut
    final int bufSize = 4096;
    byte[] buffer = new byte[bufSize];
    int length;
    try (InputStream is = blob.getBinaryStream()) {
        while ((length = is.read(buffer, 0, bufSize)) != -1) {
            out.write(buffer, 0, length);
            out.flush();
            cacheOut.write(buffer, 0, length);
        }
        out.flush();
        cacheOut.close();
    }
    

    Anything else would be a memory intensive task.