From 5a158fb76f8d0cbf79b358de5a82bf3b60248a42 Mon Sep 17 00:00:00 2001 From: Thomas Risberg Date: Fri, 20 Nov 2009 17:45:26 +0000 Subject: [PATCH] added a "releaseResourcesAfterRead" property defaulting to false and code to handle releasing any LOB resources after read if requested (SPR-5998, SPR-6209) --- .../jdbc/support/lob/OracleLobHandler.java | 174 +++++++++++++++++- 1 file changed, 164 insertions(+), 10 deletions(-) diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/support/lob/OracleLobHandler.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/support/lob/OracleLobHandler.java index ec62fe9f688..dd5af5cfc0e 100644 --- a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/support/lob/OracleLobHandler.java +++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/support/lob/OracleLobHandler.java @@ -64,12 +64,13 @@ import org.springframework.util.FileCopyUtils; * *

Coded via reflection to avoid dependencies on Oracle classes. * Even reads in Oracle constants via reflection because of different Oracle - * drivers (classes12, ojdbc14) having different constant values! As this - * LobHandler initializes Oracle classes on instantiation, do not define this - * as eager-initializing singleton if you do not want to depend on the Oracle + * drivers (classes12, ojdbc14, ojdbc5, ojdbc6) having different constant values! + * As this LobHandler initializes Oracle classes on instantiation, do not define + * this as eager-initializing singleton if you do not want to depend on the Oracle * JAR being in the class path: use "lazy-init=true" to avoid this issue. * * @author Juergen Hoeller + * @author Thomas Risberg * @since 04.12.2003 * @see #setNativeJdbcExtractor * @see oracle.sql.BLOB @@ -85,6 +86,8 @@ public class OracleLobHandler extends AbstractLobHandler { private static final String MODE_READWRITE_FIELD_NAME = "MODE_READWRITE"; + private static final String MODE_READONLY_FIELD_NAME = "MODE_READONLY"; + protected final Log logger = LogFactory.getLog(getClass()); @@ -92,6 +95,8 @@ public class OracleLobHandler extends AbstractLobHandler { private Boolean cache = Boolean.TRUE; + private Boolean releaseResourcesAfterRead = Boolean.FALSE; + private Class blobClass; private Class clobClass; @@ -100,6 +105,8 @@ public class OracleLobHandler extends AbstractLobHandler { private final Map modeReadWriteConstants = new HashMap(2); + private final Map modeReadOnlyConstants = new HashMap(2); + /** * Set an appropriate NativeJdbcExtractor to be able to retrieve the underlying @@ -123,7 +130,9 @@ public class OracleLobHandler extends AbstractLobHandler { /** * Set whether to cache the temporary LOB in the buffer cache. - * This value will be passed into BLOB/CLOB.createTemporary. Default is "true". + * This value will be passed into BLOB/CLOB.createTemporary. + * + *

Default is true. * @see oracle.sql.BLOB#createTemporary * @see oracle.sql.CLOB#createTemporary */ @@ -131,17 +140,42 @@ public class OracleLobHandler extends AbstractLobHandler { this.cache = cache; } + /** + * Set whether to agressively release any resources used by the LOB. If set to true + * then you can only read the LOB values once. Any subsequent reads will fail since the resources + * have been closed. + * + *

Setting this property to true can be useful when your queries generates large + * temporary LOBs that occupy space in the TEMPORARY tablespace or when you want to free up any + * memory allocated by the driver for the LOB reading. + * + *

Default is false. + * + * @see oracle.sql.BLOB#freeTemporary + * @see oracle.sql.CLOB#freeTemporary + * @see oracle.sql.BLOB#open + * @see oracle.sql.CLOB#open + * @see oracle.sql.BLOB#close + * @see oracle.sql.CLOB#close + * @since 3.0 + */ + public void setReleaseResourcesAfterRead(boolean releaseResources) { + this.releaseResourcesAfterRead = releaseResources; + } + /** * Retrieve the oracle.sql.BLOB and oracle.sql.CLOB * classes via reflection, and initialize the values for the - * DURATION_SESSION and MODE_READWRITE constants defined there. + * DURATION_SESSION, MODE_READWRITE and MODE_READONLY constants defined there. * @param con the Oracle Connection, for using the exact same class loader * that the Oracle driver was loaded with * @see oracle.sql.BLOB#DURATION_SESSION * @see oracle.sql.BLOB#MODE_READWRITE + * @see oracle.sql.BLOB#MODE_READONLY * @see oracle.sql.CLOB#DURATION_SESSION * @see oracle.sql.CLOB#MODE_READWRITE + * @see oracle.sql.CLOB#MODE_READONLY */ protected synchronized void initOracleDriverClasses(Connection con) { if (this.blobClass == null) { @@ -152,6 +186,8 @@ public class OracleLobHandler extends AbstractLobHandler { this.blobClass, this.blobClass.getField(DURATION_SESSION_FIELD_NAME).getInt(null)); this.modeReadWriteConstants.put( this.blobClass, this.blobClass.getField(MODE_READWRITE_FIELD_NAME).getInt(null)); + this.modeReadOnlyConstants.put( + this.blobClass, this.blobClass.getField(MODE_READONLY_FIELD_NAME).getInt(null)); // Initialize oracle.sql.CLOB class this.clobClass = con.getClass().getClassLoader().loadClass(CLOB_CLASS_NAME); @@ -159,6 +195,8 @@ public class OracleLobHandler extends AbstractLobHandler { this.clobClass, this.clobClass.getField(DURATION_SESSION_FIELD_NAME).getInt(null)); this.modeReadWriteConstants.put( this.clobClass, this.clobClass.getField(MODE_READWRITE_FIELD_NAME).getInt(null)); + this.modeReadOnlyConstants.put( + this.clobClass, this.clobClass.getField(MODE_READONLY_FIELD_NAME).getInt(null)); } catch (Exception ex) { throw new InvalidDataAccessApiUsageException( @@ -172,37 +210,153 @@ public class OracleLobHandler extends AbstractLobHandler { public byte[] getBlobAsBytes(ResultSet rs, int columnIndex) throws SQLException { logger.debug("Returning Oracle BLOB as bytes"); Blob blob = rs.getBlob(columnIndex); - return (blob != null ? blob.getBytes(1, (int) blob.length()) : null); + initializeResourcesBeforeRead(rs.getStatement().getConnection(), blob); + byte[] retVal = (blob != null ? blob.getBytes(1, (int) blob.length()) : null); + releaseResourcesAfterRead(rs.getStatement().getConnection(), blob); + return retVal; } public InputStream getBlobAsBinaryStream(ResultSet rs, int columnIndex) throws SQLException { logger.debug("Returning Oracle BLOB as binary stream"); Blob blob = rs.getBlob(columnIndex); - return (blob != null ? blob.getBinaryStream() : null); + initializeResourcesBeforeRead(rs.getStatement().getConnection(), blob); + InputStream retVal = (blob != null ? blob.getBinaryStream() : null); + releaseResourcesAfterRead(rs.getStatement().getConnection(), blob); + return retVal; } public String getClobAsString(ResultSet rs, int columnIndex) throws SQLException { logger.debug("Returning Oracle CLOB as string"); Clob clob = rs.getClob(columnIndex); - return (clob != null ? clob.getSubString(1, (int) clob.length()) : null); + initializeResourcesBeforeRead(rs.getStatement().getConnection(), clob); + String retVal = (clob != null ? clob.getSubString(1, (int) clob.length()) : null); + releaseResourcesAfterRead(rs.getStatement().getConnection(), clob); + return retVal; } public InputStream getClobAsAsciiStream(ResultSet rs, int columnIndex) throws SQLException { logger.debug("Returning Oracle CLOB as ASCII stream"); Clob clob = rs.getClob(columnIndex); - return (clob != null ? clob.getAsciiStream() : null); + initializeResourcesBeforeRead(rs.getStatement().getConnection(), clob); + InputStream retVal = (clob != null ? clob.getAsciiStream() : null); + releaseResourcesAfterRead(rs.getStatement().getConnection(), clob); + return retVal; } public Reader getClobAsCharacterStream(ResultSet rs, int columnIndex) throws SQLException { logger.debug("Returning Oracle CLOB as character stream"); Clob clob = rs.getClob(columnIndex); - return (clob != null ? clob.getCharacterStream() : null); + initializeResourcesBeforeRead(rs.getStatement().getConnection(), clob); + Reader retVal = (clob != null ? clob.getCharacterStream() : null); + releaseResourcesAfterRead(rs.getStatement().getConnection(), clob); + return retVal; } public LobCreator getLobCreator() { return new OracleLobCreator(); } + /** + * Initialize any LOB resources before a read is done. + * + *

This implementation calls + * BLOB.open(BLOB.MODE_READONLY) or CLOB.open(CLOB.MODE_READONLY) + * on any non-temporary LOBs + * if releaseResourcesAfterRead property is set to true. + *

This method can be overridden by sublcasses if different behavior is desired. + * @param con the connection to be usde for initilization + * @param lob the LOB to initialize + */ + protected void initializeResourcesBeforeRead(Connection con, Object lob) { + if (releaseResourcesAfterRead) { + initOracleDriverClasses(con); + try { + /* + if (!((BLOB)lob.isTemporary() { + */ + Method isTemporary = lob.getClass().getMethod("isTemporary"); + Boolean temporary = (Boolean) isTemporary.invoke(lob); + if (!temporary) { + /* + ((BLOB)lob).open(BLOB.MODE_READONLY); + */ + Method open = lob.getClass().getMethod("open", int.class); + open.invoke(lob, modeReadOnlyConstants.get(lob.getClass())); + } + } + catch (InvocationTargetException ex) { + logger.error("Could not open Oracle LOB", ex.getTargetException()); + } + catch (Exception ex) { + throw new DataAccessResourceFailureException("Could not open Oracle LOB", ex); + } + } + } + + /** + * Release any LOB resources after read is complete. + * + *

If releaseResourcesAfterRead property is set to true + * then this implementation calls + * BLOB.close() or CLOB.close() + * on any non-temporary LOBs that are open or + * BLOB.freeTemporary() or CLOB.freeTemporary() + * on any temporary LOBs. + * + *

This method can be overridden by sublcasses if different behavior is desired. + * @param con the connection to be usde for initilization + * @param lob the LOB to initialize + */ + protected void releaseResourcesAfterRead(Connection con, Object lob) { + if (releaseResourcesAfterRead) { + initOracleDriverClasses(con); + Boolean temporary = Boolean.FALSE; + try { + /* + if (((BLOB)lob.isTemporary() { + */ + Method isTemporary = lob.getClass().getMethod("isTemporary"); + temporary = (Boolean) isTemporary.invoke(lob); + if (temporary) { + /* + ((BLOB)lob).freeTemporary(); + */ + Method freeTemporary = lob.getClass().getMethod("freeTemporary"); + freeTemporary.invoke(lob); + } + else { + /* + if (((BLOB)lob.isOpen() { + */ + Method isOpen = lob.getClass().getMethod("isOpen"); + Boolean open = (Boolean) isOpen.invoke(lob); + if (open) { + /* + ((BLOB)lob).close(); + */ + Method close = lob.getClass().getMethod("close"); + close.invoke(lob); + } + } + } + catch (InvocationTargetException ex) { + if (temporary) { + logger.error("Could not free Oracle LOB", ex.getTargetException()); + } + else { + logger.error("Could not close Oracle LOB", ex.getTargetException()); + } + } + catch (Exception ex) { + if (temporary) { + throw new DataAccessResourceFailureException("Could not free Oracle LOB", ex); + } + else { + throw new DataAccessResourceFailureException("Could not close Oracle LOB", ex); + } + } + } + } /** * LobCreator implementation for Oracle databases.