Skip to content

Commit 2bd584c

Browse files
jhoellerunknown
authored and
unknown
committed
Added "createTemporaryLob" flag to DefaultLobHandler, using JDBC 4.0's createBlob/Clob mechanism
Issue: SPR-10339
1 parent 7ce0406 commit 2bd584c

File tree

3 files changed

+237
-31
lines changed

3 files changed

+237
-31
lines changed

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

Lines changed: 52 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2013 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -32,30 +32,36 @@
3232
import org.apache.commons.logging.LogFactory;
3333

3434
/**
35-
* Default implementation of the {@link LobHandler} interface. Invokes
36-
* the direct accessor methods that {@code java.sql.ResultSet}
35+
* Default implementation of the {@link LobHandler} interface.
36+
* Invokes the direct accessor methods that {@code java.sql.ResultSet}
3737
* and {@code java.sql.PreparedStatement} offer.
3838
*
3939
* <p>This LobHandler should work for any JDBC driver that is JDBC compliant
4040
* in terms of the spec's suggestions regarding simple BLOB and CLOB handling.
41-
* This does not apply to Oracle 9i, and only to a limited degree to Oracle 10g!
42-
* As a consequence, use {@link OracleLobHandler} for accessing Oracle BLOBs/CLOBs.
41+
* This does not apply to Oracle 9i's drivers at all; as of Oracle 10g,
42+
* it does work but may still come with LOB size limitations. Consider using
43+
* recent Oracle drivers even when working against an older database server.
44+
* See the {@link LobHandler} javadoc for the full set of recommendations.
4345
*
4446
* <p>Some JDBC drivers require values with a BLOB/CLOB target column to be
45-
* explicitly set through the JDBC {@code setBlob} / {@code setClob}
46-
* API: for example, PostgreSQL's driver. Switch the {@link #setWrapAsLob "wrapAsLob"}
47+
* explicitly set through the JDBC {@code setBlob} / {@code setClob} API:
48+
* for example, PostgreSQL's driver. Switch the {@link #setWrapAsLob "wrapAsLob"}
4749
* property to "true" when operating against such a driver.
4850
*
4951
* <p>On JDBC 4.0, this LobHandler also supports streaming the BLOB/CLOB content
5052
* via the {@code setBlob} / {@code setClob} variants that take a stream
5153
* argument directly. Consider switching the {@link #setStreamAsLob "streamAsLob"}
5254
* property to "true" when operating against a fully compliant JDBC 4.0 driver.
5355
*
54-
* <p>See the {@link LobHandler} javadoc for a summary of recommendations.
56+
* <p>Finally, primarily as a direct equivalent to {@link OracleLobHandler},
57+
* this LobHandler also supports the creation of temporary BLOB/CLOB objects.
58+
* Consider switching the {@link #setCreateTemporaryLob "createTemporaryLob"}
59+
* property to "true" when "streamAsLob" happens to run into LOB size limitations.
60+
*
61+
* <p>See the {@link LobHandler} interface javadoc for a summary of recommendations.
5562
*
5663
* @author Juergen Hoeller
5764
* @since 04.12.2003
58-
* @see #setStreamAsLob
5965
* @see java.sql.ResultSet#getBytes
6066
* @see java.sql.ResultSet#getBinaryStream
6167
* @see java.sql.ResultSet#getString
@@ -75,15 +81,18 @@ public class DefaultLobHandler extends AbstractLobHandler {
7581

7682
private boolean streamAsLob = false;
7783

84+
private boolean createTemporaryLob = false;
85+
7886

7987
/**
8088
* Specify whether to submit a byte array / String to the JDBC driver
8189
* wrapped in a JDBC Blob / Clob object, using the JDBC {@code setBlob} /
8290
* {@code setClob} method with a Blob / Clob argument.
8391
* <p>Default is "false", using the common JDBC 2.0 {@code setBinaryStream}
84-
* / {@code setCharacterStream} method for setting the content.
85-
* Switch this to "true" for explicit Blob / Clob wrapping against
86-
* JDBC drivers that are known to require such wrapping (e.g. PostgreSQL's).
92+
* / {@code setCharacterStream} method for setting the content. Switch this
93+
* to "true" for explicit Blob / Clob wrapping against JDBC drivers that
94+
* are known to require such wrapping (e.g. PostgreSQL's for access to OID
95+
* columns, whereas BYTEA columns need to be accessed the standard way).
8796
* <p>This setting affects byte array / String arguments as well as stream
8897
* arguments, unless {@link #setStreamAsLob "streamAsLob"} overrides this
8998
* handling to use JDBC 4.0's new explicit streaming support (if available).
@@ -100,7 +109,7 @@ public void setWrapAsLob(boolean wrapAsLob) {
100109
* {@code setClob} method with a stream argument.
101110
* <p>Default is "false", using the common JDBC 2.0 {@code setBinaryStream}
102111
* / {@code setCharacterStream} method for setting the content.
103-
* Switch this to "true" for explicit JDBC 4.0 usage, provided that your
112+
* Switch this to "true" for explicit JDBC 4.0 streaming, provided that your
104113
* JDBC driver actually supports those JDBC 4.0 operations (e.g. Derby's).
105114
* <p>This setting affects stream arguments as well as byte array / String
106115
* arguments, requiring JDBC 4.0 support. For supporting LOB content against
@@ -112,6 +121,23 @@ public void setStreamAsLob(boolean streamAsLob) {
112121
this.streamAsLob = streamAsLob;
113122
}
114123

124+
/**
125+
* Specify whether to copy a byte array / String into a temporary JDBC
126+
* Blob / Clob object created through the JDBC 4.0 {@code createBlob} /
127+
* {@code createClob} methods.
128+
* <p>Default is "false", using the common JDBC 2.0 {@code setBinaryStream}
129+
* / {@code setCharacterStream} method for setting the content. Switch this
130+
* to "true" for explicit Blob / Clob creation using JDBC 4.0.
131+
* <p>This setting affects stream arguments as well as byte array / String
132+
* arguments, requiring JDBC 4.0 support. For supporting LOB content against
133+
* JDBC 3.0, check out the {@link #setWrapAsLob "wrapAsLob"} setting.
134+
* @see java.sql.Connection#createBlob()
135+
* @see java.sql.Connection#createClob()
136+
*/
137+
public void setCreateTemporaryLob(boolean createTemporaryLob) {
138+
this.createTemporaryLob = createTemporaryLob;
139+
}
140+
115141

116142
public byte[] getBlobAsBytes(ResultSet rs, int columnIndex) throws SQLException {
117143
logger.debug("Returning BLOB as bytes");
@@ -169,12 +195,12 @@ public Reader getClobAsCharacterStream(ResultSet rs, int columnIndex) throws SQL
169195
}
170196

171197
public LobCreator getLobCreator() {
172-
return new DefaultLobCreator();
198+
return (this.createTemporaryLob ? new TemporaryLobCreator() : new DefaultLobCreator());
173199
}
174200

175201

176202
/**
177-
* Default LobCreator implementation as inner class.
203+
* Default LobCreator implementation as an inner class.
178204
* Can be subclassed in DefaultLobHandler extensions.
179205
*/
180206
protected class DefaultLobCreator implements LobCreator {
@@ -268,15 +294,10 @@ public void setClobAsAsciiStream(
268294
PreparedStatement ps, int paramIndex, InputStream asciiStream, int contentLength)
269295
throws SQLException {
270296

271-
if (streamAsLob || wrapAsLob) {
297+
if (streamAsLob) {
272298
if (asciiStream != null) {
273299
try {
274-
if (streamAsLob) {
275-
ps.setClob(paramIndex, new InputStreamReader(asciiStream, "US-ASCII"), contentLength);
276-
}
277-
else {
278-
ps.setClob(paramIndex, new PassThroughClob(asciiStream, contentLength));
279-
}
300+
ps.setClob(paramIndex, new InputStreamReader(asciiStream, "US-ASCII"), contentLength);
280301
}
281302
catch (UnsupportedEncodingException ex) {
282303
throw new SQLException("US-ASCII encoding not supported: " + ex);
@@ -286,6 +307,14 @@ public void setClobAsAsciiStream(
286307
ps.setClob(paramIndex, (Clob) null);
287308
}
288309
}
310+
else if (wrapAsLob) {
311+
if (asciiStream != null) {
312+
ps.setClob(paramIndex, new PassThroughClob(asciiStream, contentLength));
313+
}
314+
else {
315+
ps.setClob(paramIndex, (Clob) null);
316+
}
317+
}
289318
else {
290319
ps.setAsciiStream(paramIndex, asciiStream, contentLength);
291320
}
@@ -325,7 +354,7 @@ else if (wrapAsLob) {
325354
}
326355

327356
public void close() {
328-
// nothing to do here
357+
// nothing to do when not creating temporary LOBs
329358
}
330359
}
331360

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

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2013 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,7 +24,7 @@
2424
/**
2525
* Abstraction for handling large binary fields and large text fields in
2626
* specific databases, no matter if represented as simple types or Large OBjects.
27-
* Its main purpose is to isolate Oracle's peculiar handling of LOBs in
27+
* Its main purpose is to isolate Oracle 9i's peculiar handling of LOBs in
2828
* {@link OracleLobHandler}; most other databases should be able to work
2929
* with the provided {@link DefaultLobHandler}.
3030
*
@@ -45,8 +45,10 @@
4545
* proprietary BLOB/CLOB API, and additionally doesn't accept large streams for
4646
* PreparedStatement's corresponding setter methods. Therefore, you need to use
4747
* {@link OracleLobHandler} there, which uses Oracle's BLOB/CLOB API for both types
48-
* of access. The Oracle 10g JDBC driver should basically work with
49-
* {@link DefaultLobHandler} as well, with some limitations in terms of LOB sizes.
48+
* of access. The Oracle 10g+ JDBC driver will work with {@link DefaultLobHandler}
49+
* as well, with some limitations in terms of LOB sizes depending on DBMS setup;
50+
* as of Oracle 11g (or actually, using the 11g driver even against older databases),
51+
* there should be no need to use {@link OracleLobHandler} at all anymore.
5052
*
5153
* <p>Of course, you need to declare different field types for each database.
5254
* In Oracle, any binary content needs to go into a BLOB, and all character content
@@ -57,12 +59,20 @@
5759
*
5860
* <p><b>Summarizing the recommended options (for actual LOB fields):</b>
5961
* <ul>
60-
* <li><b>JDBC 4.0 driver:</b> {@link DefaultLobHandler} with {@code streamAsLob=true}.
61-
* <li><b>PostgreSQL:</b> {@link DefaultLobHandler} with {@code wrapAsLob=true}.
62-
* <li><b>Oracle 9i/10g:</b> {@link OracleLobHandler} with a connection-pool-specific
62+
* <li><b>JDBC 4.0 driver (including Oracle 11g driver):</b> Use {@link DefaultLobHandler},
63+
* potentially with {@code streamAsLob=true} if your database driver requires that
64+
* hint when populating a LOB field. Fall back to {@code createTemporaryLob=true}
65+
* if you happen to run into LOB size limitations with your (Oracle) database setup.
66+
* <li><b>Oracle 10g driver:</b> Use {@link DefaultLobHandler} with standard setup.
67+
* On Oracle 10.1, set the "SetBigStringTryClob" connection property; as of Oracle 10.2,
68+
* DefaultLobHandler should work with standard setup out of the box. Alternatively,
69+
* consider using the proprietary {@link OracleLobHandler} (see below).
70+
* <li><b>Oracle 9i driver:</b> Use {@link OracleLobHandler} with a connection-pool-specific
6371
* {@link OracleLobHandler#setNativeJdbcExtractor NativeJdbcExtractor}.
72+
* <li><b>PostgreSQL:</b> Configure {@link DefaultLobHandler} with {@code wrapAsLob=true},
73+
* and use that LobHandler to access OID columns (but not BYTEA) in your database tables.
6474
* <li>For all other database drivers (and for non-LOB fields that might potentially
65-
* turn into LOBs on some databases): a plain {@link DefaultLobHandler}.
75+
* turn into LOBs on some databases): Simply use a plain {@link DefaultLobHandler}.
6676
* </ul>
6777
*
6878
* @author Juergen Hoeller
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/*
2+
* Copyright 2002-2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.jdbc.support.lob;
18+
19+
import java.io.IOException;
20+
import java.io.InputStream;
21+
import java.io.Reader;
22+
import java.sql.Blob;
23+
import java.sql.Clob;
24+
import java.sql.PreparedStatement;
25+
import java.sql.SQLException;
26+
import java.util.LinkedHashSet;
27+
import java.util.Set;
28+
29+
import org.apache.commons.logging.Log;
30+
import org.apache.commons.logging.LogFactory;
31+
32+
import org.springframework.dao.DataAccessResourceFailureException;
33+
import org.springframework.util.FileCopyUtils;
34+
35+
/**
36+
* {@link LobCreator} implementation based on temporary LOBs,
37+
* using JDBC 4.0's {@link java.sql.Connection#createBlob()} /
38+
* {@link java.sql.Connection#createClob()} mechanism.
39+
*
40+
* <p>Used by DefaultLobHandler's {@link DefaultLobHandler#setCreateTemporaryLob} mode.
41+
* Can also be used directly to reuse the tracking and freeing of temporary LOBs.
42+
*
43+
* @author Juergen Hoeller
44+
* @since 3.2.2
45+
* @see DefaultLobHandler#setCreateTemporaryLob
46+
* @see java.sql.Connection#createBlob()
47+
* @see java.sql.Connection#createClob()
48+
*/
49+
public class TemporaryLobCreator implements LobCreator {
50+
51+
protected static final Log logger = LogFactory.getLog(TemporaryLobCreator.class);
52+
53+
private final Set<Blob> temporaryBlobs = new LinkedHashSet<Blob>(1);
54+
55+
private final Set<Clob> temporaryClobs = new LinkedHashSet<Clob>(1);
56+
57+
58+
public void setBlobAsBytes(PreparedStatement ps, int paramIndex, byte[] content)
59+
throws SQLException {
60+
61+
Blob blob = ps.getConnection().createBlob();
62+
blob.setBytes(1, content);
63+
64+
this.temporaryBlobs.add(blob);
65+
ps.setBlob(paramIndex, blob);
66+
67+
if (logger.isDebugEnabled()) {
68+
logger.debug(content != null ? "Copied bytes into temporary BLOB with length " + content.length :
69+
"Set BLOB to null");
70+
}
71+
}
72+
73+
public void setBlobAsBinaryStream(
74+
PreparedStatement ps, int paramIndex, InputStream binaryStream, int contentLength)
75+
throws SQLException {
76+
77+
Blob blob = ps.getConnection().createBlob();
78+
try {
79+
FileCopyUtils.copy(binaryStream, blob.setBinaryStream(1));
80+
}
81+
catch (IOException ex) {
82+
throw new DataAccessResourceFailureException("Could not copy into LOB stream", ex);
83+
}
84+
85+
this.temporaryBlobs.add(blob);
86+
ps.setBlob(paramIndex, blob);
87+
88+
if (logger.isDebugEnabled()) {
89+
logger.debug(binaryStream != null ?
90+
"Copied binary stream into temporary BLOB with length " + contentLength :
91+
"Set BLOB to null");
92+
}
93+
}
94+
95+
public void setClobAsString(PreparedStatement ps, int paramIndex, String content)
96+
throws SQLException {
97+
98+
Clob clob = ps.getConnection().createClob();
99+
clob.setString(1, content);
100+
101+
this.temporaryClobs.add(clob);
102+
ps.setClob(paramIndex, clob);
103+
104+
if (logger.isDebugEnabled()) {
105+
logger.debug(content != null ? "Copied string into temporary CLOB with length " + content.length() :
106+
"Set CLOB to null");
107+
}
108+
}
109+
110+
public void setClobAsAsciiStream(
111+
PreparedStatement ps, int paramIndex, InputStream asciiStream, int contentLength)
112+
throws SQLException {
113+
114+
Clob clob = ps.getConnection().createClob();
115+
try {
116+
FileCopyUtils.copy(asciiStream, clob.setAsciiStream(1));
117+
}
118+
catch (IOException ex) {
119+
throw new DataAccessResourceFailureException("Could not copy into LOB stream", ex);
120+
}
121+
122+
this.temporaryClobs.add(clob);
123+
ps.setClob(paramIndex, clob);
124+
125+
if (logger.isDebugEnabled()) {
126+
logger.debug(asciiStream != null ?
127+
"Copied ASCII stream into temporary CLOB with length " + contentLength :
128+
"Set CLOB to null");
129+
}
130+
}
131+
132+
public void setClobAsCharacterStream(
133+
PreparedStatement ps, int paramIndex, Reader characterStream, int contentLength)
134+
throws SQLException {
135+
136+
Clob clob = ps.getConnection().createClob();
137+
try {
138+
FileCopyUtils.copy(characterStream, clob.setCharacterStream(1));
139+
}
140+
catch (IOException ex) {
141+
throw new DataAccessResourceFailureException("Could not copy into LOB stream", ex);
142+
}
143+
144+
this.temporaryClobs.add(clob);
145+
ps.setClob(paramIndex, clob);
146+
147+
if (logger.isDebugEnabled()) {
148+
logger.debug(characterStream != null ?
149+
"Copied character stream into temporary CLOB with length " + contentLength :
150+
"Set CLOB to null");
151+
}
152+
}
153+
154+
public void close() {
155+
try {
156+
for (Blob blob : this.temporaryBlobs) {
157+
blob.free();
158+
}
159+
for (Clob clob : this.temporaryClobs) {
160+
clob.free();
161+
}
162+
}
163+
catch (SQLException ex) {
164+
logger.error("Could not free LOB", ex);
165+
}
166+
}
167+
}

0 commit comments

Comments
 (0)