diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/DefaultLobHandler.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/DefaultLobHandler.java index 6964172e962..e570be24f03 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/DefaultLobHandler.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/DefaultLobHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,18 +32,20 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** - * Default implementation of the {@link LobHandler} interface. Invokes - * the direct accessor methods that {@code java.sql.ResultSet} + * Default implementation of the {@link LobHandler} interface. + * Invokes the direct accessor methods that {@code java.sql.ResultSet} * and {@code java.sql.PreparedStatement} offer. * *

This LobHandler should work for any JDBC driver that is JDBC compliant * in terms of the spec's suggestions regarding simple BLOB and CLOB handling. - * This does not apply to Oracle 9i, and only to a limited degree to Oracle 10g! - * As a consequence, use {@link OracleLobHandler} for accessing Oracle BLOBs/CLOBs. + * This does not apply to Oracle 9i's drivers at all; as of Oracle 10g, + * it does work but may still come with LOB size limitations. Consider using + * recent Oracle drivers even when working against an older database server. + * See the {@link LobHandler} javadoc for the full set of recommendations. * *

Some JDBC drivers require values with a BLOB/CLOB target column to be - * explicitly set through the JDBC {@code setBlob} / {@code setClob} - * API: for example, PostgreSQL's driver. Switch the {@link #setWrapAsLob "wrapAsLob"} + * explicitly set through the JDBC {@code setBlob} / {@code setClob} API: + * for example, PostgreSQL's driver. Switch the {@link #setWrapAsLob "wrapAsLob"} * property to "true" when operating against such a driver. * *

On JDBC 4.0, this LobHandler also supports streaming the BLOB/CLOB content @@ -51,11 +53,15 @@ import org.apache.commons.logging.LogFactory; * argument directly. Consider switching the {@link #setStreamAsLob "streamAsLob"} * property to "true" when operating against a fully compliant JDBC 4.0 driver. * - *

See the {@link LobHandler} javadoc for a summary of recommendations. + *

Finally, primarily as a direct equivalent to {@link OracleLobHandler}, + * this LobHandler also supports the creation of temporary BLOB/CLOB objects. + * Consider switching the {@link #setCreateTemporaryLob "createTemporaryLob"} + * property to "true" when "streamAsLob" happens to run into LOB size limitations. + * + *

See the {@link LobHandler} interface javadoc for a summary of recommendations. * * @author Juergen Hoeller * @since 04.12.2003 - * @see #setStreamAsLob * @see java.sql.ResultSet#getBytes * @see java.sql.ResultSet#getBinaryStream * @see java.sql.ResultSet#getString @@ -75,15 +81,18 @@ public class DefaultLobHandler extends AbstractLobHandler { private boolean streamAsLob = false; + private boolean createTemporaryLob = false; + /** * Specify whether to submit a byte array / String to the JDBC driver * wrapped in a JDBC Blob / Clob object, using the JDBC {@code setBlob} / * {@code setClob} method with a Blob / Clob argument. *

Default is "false", using the common JDBC 2.0 {@code setBinaryStream} - * / {@code setCharacterStream} method for setting the content. - * Switch this to "true" for explicit Blob / Clob wrapping against - * JDBC drivers that are known to require such wrapping (e.g. PostgreSQL's). + * / {@code setCharacterStream} method for setting the content. Switch this + * to "true" for explicit Blob / Clob wrapping against JDBC drivers that + * are known to require such wrapping (e.g. PostgreSQL's for access to OID + * columns, whereas BYTEA columns need to be accessed the standard way). *

This setting affects byte array / String arguments as well as stream * arguments, unless {@link #setStreamAsLob "streamAsLob"} overrides this * handling to use JDBC 4.0's new explicit streaming support (if available). @@ -100,7 +109,7 @@ public class DefaultLobHandler extends AbstractLobHandler { * {@code setClob} method with a stream argument. *

Default is "false", using the common JDBC 2.0 {@code setBinaryStream} * / {@code setCharacterStream} method for setting the content. - * Switch this to "true" for explicit JDBC 4.0 usage, provided that your + * Switch this to "true" for explicit JDBC 4.0 streaming, provided that your * JDBC driver actually supports those JDBC 4.0 operations (e.g. Derby's). *

This setting affects stream arguments as well as byte array / String * arguments, requiring JDBC 4.0 support. For supporting LOB content against @@ -112,6 +121,23 @@ public class DefaultLobHandler extends AbstractLobHandler { this.streamAsLob = streamAsLob; } + /** + * Specify whether to copy a byte array / String into a temporary JDBC + * Blob / Clob object created through the JDBC 4.0 {@code createBlob} / + * {@code createClob} methods. + *

Default is "false", using the common JDBC 2.0 {@code setBinaryStream} + * / {@code setCharacterStream} method for setting the content. Switch this + * to "true" for explicit Blob / Clob creation using JDBC 4.0. + *

This setting affects stream arguments as well as byte array / String + * arguments, requiring JDBC 4.0 support. For supporting LOB content against + * JDBC 3.0, check out the {@link #setWrapAsLob "wrapAsLob"} setting. + * @see java.sql.Connection#createBlob() + * @see java.sql.Connection#createClob() + */ + public void setCreateTemporaryLob(boolean createTemporaryLob) { + this.createTemporaryLob = createTemporaryLob; + } + public byte[] getBlobAsBytes(ResultSet rs, int columnIndex) throws SQLException { logger.debug("Returning BLOB as bytes"); @@ -169,12 +195,12 @@ public class DefaultLobHandler extends AbstractLobHandler { } public LobCreator getLobCreator() { - return new DefaultLobCreator(); + return (this.createTemporaryLob ? new TemporaryLobCreator() : new DefaultLobCreator()); } /** - * Default LobCreator implementation as inner class. + * Default LobCreator implementation as an inner class. * Can be subclassed in DefaultLobHandler extensions. */ protected class DefaultLobCreator implements LobCreator { @@ -268,15 +294,10 @@ public class DefaultLobHandler extends AbstractLobHandler { PreparedStatement ps, int paramIndex, InputStream asciiStream, int contentLength) throws SQLException { - if (streamAsLob || wrapAsLob) { + if (streamAsLob) { if (asciiStream != null) { try { - if (streamAsLob) { - ps.setClob(paramIndex, new InputStreamReader(asciiStream, "US-ASCII"), contentLength); - } - else { - ps.setClob(paramIndex, new PassThroughClob(asciiStream, contentLength)); - } + ps.setClob(paramIndex, new InputStreamReader(asciiStream, "US-ASCII"), contentLength); } catch (UnsupportedEncodingException ex) { throw new SQLException("US-ASCII encoding not supported: " + ex); @@ -286,6 +307,14 @@ public class DefaultLobHandler extends AbstractLobHandler { ps.setClob(paramIndex, (Clob) null); } } + else if (wrapAsLob) { + if (asciiStream != null) { + ps.setClob(paramIndex, new PassThroughClob(asciiStream, contentLength)); + } + else { + ps.setClob(paramIndex, (Clob) null); + } + } else { ps.setAsciiStream(paramIndex, asciiStream, contentLength); } @@ -325,7 +354,7 @@ public class DefaultLobHandler extends AbstractLobHandler { } public void close() { - // nothing to do here + // nothing to do when not creating temporary LOBs } } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/LobHandler.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/LobHandler.java index 251a63a0e8d..07e1847e4f5 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/LobHandler.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/LobHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ import java.sql.SQLException; /** * Abstraction for handling large binary fields and large text fields in * specific databases, no matter if represented as simple types or Large OBjects. - * Its main purpose is to isolate Oracle's peculiar handling of LOBs in + * Its main purpose is to isolate Oracle 9i's peculiar handling of LOBs in * {@link OracleLobHandler}; most other databases should be able to work * with the provided {@link DefaultLobHandler}. * @@ -45,8 +45,10 @@ import java.sql.SQLException; * proprietary BLOB/CLOB API, and additionally doesn't accept large streams for * PreparedStatement's corresponding setter methods. Therefore, you need to use * {@link OracleLobHandler} there, which uses Oracle's BLOB/CLOB API for both types - * of access. The Oracle 10g JDBC driver should basically work with - * {@link DefaultLobHandler} as well, with some limitations in terms of LOB sizes. + * of access. The Oracle 10g+ JDBC driver will work with {@link DefaultLobHandler} + * as well, with some limitations in terms of LOB sizes depending on DBMS setup; + * as of Oracle 11g (or actually, using the 11g driver even against older databases), + * there should be no need to use {@link OracleLobHandler} at all anymore. * *

Of course, you need to declare different field types for each database. * In Oracle, any binary content needs to go into a BLOB, and all character content @@ -57,12 +59,20 @@ import java.sql.SQLException; * *

Summarizing the recommended options (for actual LOB fields): *

* * @author Juergen Hoeller diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/TemporaryLobCreator.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/TemporaryLobCreator.java new file mode 100644 index 00000000000..f8c52660cb7 --- /dev/null +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/TemporaryLobCreator.java @@ -0,0 +1,167 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.jdbc.support.lob; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.util.FileCopyUtils; + +/** + * {@link LobCreator} implementation based on temporary LOBs, + * using JDBC 4.0's {@link java.sql.Connection#createBlob()} / + * {@link java.sql.Connection#createClob()} mechanism. + * + *

Used by DefaultLobHandler's {@link DefaultLobHandler#setCreateTemporaryLob} mode. + * Can also be used directly to reuse the tracking and freeing of temporary LOBs. + * + * @author Juergen Hoeller + * @since 3.2.2 + * @see DefaultLobHandler#setCreateTemporaryLob + * @see java.sql.Connection#createBlob() + * @see java.sql.Connection#createClob() + */ +public class TemporaryLobCreator implements LobCreator { + + protected static final Log logger = LogFactory.getLog(TemporaryLobCreator.class); + + private final Set temporaryBlobs = new LinkedHashSet(1); + + private final Set temporaryClobs = new LinkedHashSet(1); + + + public void setBlobAsBytes(PreparedStatement ps, int paramIndex, byte[] content) + throws SQLException { + + Blob blob = ps.getConnection().createBlob(); + blob.setBytes(1, content); + + this.temporaryBlobs.add(blob); + ps.setBlob(paramIndex, blob); + + if (logger.isDebugEnabled()) { + logger.debug(content != null ? "Copied bytes into temporary BLOB with length " + content.length : + "Set BLOB to null"); + } + } + + public void setBlobAsBinaryStream( + PreparedStatement ps, int paramIndex, InputStream binaryStream, int contentLength) + throws SQLException { + + Blob blob = ps.getConnection().createBlob(); + try { + FileCopyUtils.copy(binaryStream, blob.setBinaryStream(1)); + } + catch (IOException ex) { + throw new DataAccessResourceFailureException("Could not copy into LOB stream", ex); + } + + this.temporaryBlobs.add(blob); + ps.setBlob(paramIndex, blob); + + if (logger.isDebugEnabled()) { + logger.debug(binaryStream != null ? + "Copied binary stream into temporary BLOB with length " + contentLength : + "Set BLOB to null"); + } + } + + public void setClobAsString(PreparedStatement ps, int paramIndex, String content) + throws SQLException { + + Clob clob = ps.getConnection().createClob(); + clob.setString(1, content); + + this.temporaryClobs.add(clob); + ps.setClob(paramIndex, clob); + + if (logger.isDebugEnabled()) { + logger.debug(content != null ? "Copied string into temporary CLOB with length " + content.length() : + "Set CLOB to null"); + } + } + + public void setClobAsAsciiStream( + PreparedStatement ps, int paramIndex, InputStream asciiStream, int contentLength) + throws SQLException { + + Clob clob = ps.getConnection().createClob(); + try { + FileCopyUtils.copy(asciiStream, clob.setAsciiStream(1)); + } + catch (IOException ex) { + throw new DataAccessResourceFailureException("Could not copy into LOB stream", ex); + } + + this.temporaryClobs.add(clob); + ps.setClob(paramIndex, clob); + + if (logger.isDebugEnabled()) { + logger.debug(asciiStream != null ? + "Copied ASCII stream into temporary CLOB with length " + contentLength : + "Set CLOB to null"); + } + } + + public void setClobAsCharacterStream( + PreparedStatement ps, int paramIndex, Reader characterStream, int contentLength) + throws SQLException { + + Clob clob = ps.getConnection().createClob(); + try { + FileCopyUtils.copy(characterStream, clob.setCharacterStream(1)); + } + catch (IOException ex) { + throw new DataAccessResourceFailureException("Could not copy into LOB stream", ex); + } + + this.temporaryClobs.add(clob); + ps.setClob(paramIndex, clob); + + if (logger.isDebugEnabled()) { + logger.debug(characterStream != null ? + "Copied character stream into temporary CLOB with length " + contentLength : + "Set CLOB to null"); + } + } + + public void close() { + try { + for (Blob blob : this.temporaryBlobs) { + blob.free(); + } + for (Clob clob : this.temporaryClobs) { + clob.free(); + } + } + catch (SQLException ex) { + logger.error("Could not free LOB", ex); + } + } +}