Added "createTemporaryLob" flag to DefaultLobHandler, using JDBC 4.0's createBlob/Clob mechanism
Issue: SPR-10339
This commit is contained in:
parent
7ce0406fbc
commit
2bd584ca75
|
|
@ -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.
|
||||
*
|
||||
* <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;
|
|||
* 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 {
|
|||
|
||||
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 {
|
|||
* {@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 {
|
|||
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 {
|
|||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
* <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;
|
|||
*
|
||||
* <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
|
||||
|
|
|
|||
|
|
@ -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…
Reference in New Issue