Browse Source

Added "createTemporaryLob" flag to DefaultLobHandler, using JDBC 4.0's createBlob/Clob mechanism

Issue: SPR-10339
pull/244/merge
Juergen Hoeller 12 years ago committed by unknown
parent
commit
2bd584ca75
  1. 75
      spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/DefaultLobHandler.java
  2. 26
      spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/LobHandler.java
  3. 167
      spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/TemporaryLobCreator.java

75
spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/DefaultLobHandler.java

@ -1,5 +1,5 @@ @@ -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; @@ -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.
*
* <p>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.
*
* <p>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.
*
* <p>On JDBC 4.0, this LobHandler also supports streaming the BLOB/CLOB content
@ -51,11 +53,15 @@ import org.apache.commons.logging.LogFactory; @@ -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.
*
* <p>See the {@link LobHandler} javadoc for a summary of recommendations.
* <p>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.
*
* <p>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 { @@ -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.
* <p>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).
* <p>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 { @@ -100,7 +109,7 @@ public class DefaultLobHandler extends AbstractLobHandler {
* {@code setClob} method with a stream argument.
* <p>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).
* <p>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 { @@ -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.
* <p>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.
* <p>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 { @@ -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 { @@ -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 { @@ -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 { @@ -325,7 +354,7 @@ public class DefaultLobHandler extends AbstractLobHandler {
}
public void close() {
// nothing to do here
// nothing to do when not creating temporary LOBs
}
}

26
spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/LobHandler.java

@ -1,5 +1,5 @@ @@ -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; @@ -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; @@ -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.
*
* <p>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; @@ -57,12 +59,20 @@ import java.sql.SQLException;
*
* <p><b>Summarizing the recommended options (for actual LOB fields):</b>
* <ul>
* <li><b>JDBC 4.0 driver:</b> {@link DefaultLobHandler} with {@code streamAsLob=true}.
* <li><b>PostgreSQL:</b> {@link DefaultLobHandler} with {@code wrapAsLob=true}.
* <li><b>Oracle 9i/10g:</b> {@link OracleLobHandler} with a connection-pool-specific
* <li><b>JDBC 4.0 driver (including Oracle 11g driver):</b> Use {@link DefaultLobHandler},
* potentially with {@code streamAsLob=true} if your database driver requires that
* hint when populating a LOB field. Fall back to {@code createTemporaryLob=true}
* if you happen to run into LOB size limitations with your (Oracle) database setup.
* <li><b>Oracle 10g driver:</b> Use {@link DefaultLobHandler} with standard setup.
* On Oracle 10.1, set the "SetBigStringTryClob" connection property; as of Oracle 10.2,
* DefaultLobHandler should work with standard setup out of the box. Alternatively,
* consider using the proprietary {@link OracleLobHandler} (see below).
* <li><b>Oracle 9i driver:</b> Use {@link OracleLobHandler} with a connection-pool-specific
* {@link OracleLobHandler#setNativeJdbcExtractor NativeJdbcExtractor}.
* <li><b>PostgreSQL:</b> Configure {@link DefaultLobHandler} with {@code wrapAsLob=true},
* and use that LobHandler to access OID columns (but not BYTEA) in your database tables.
* <li>For all other database drivers (and for non-LOB fields that might potentially
* turn into LOBs on some databases): a plain {@link DefaultLobHandler}.
* turn into LOBs on some databases): Simply use a plain {@link DefaultLobHandler}.
* </ul>
*
* @author Juergen Hoeller

167
spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/TemporaryLobCreator.java

@ -0,0 +1,167 @@ @@ -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.
*
* <p>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<Blob> temporaryBlobs = new LinkedHashSet<Blob>(1);
private final Set<Clob> temporaryClobs = new LinkedHashSet<Clob>(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);
}
}
}
Loading…
Cancel
Save