001/* 002 * Copyright 2002-2014 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.jdbc.support.lob; 018 019import java.io.ByteArrayInputStream; 020import java.io.InputStream; 021import java.io.InputStreamReader; 022import java.io.Reader; 023import java.io.StringReader; 024import java.io.UnsupportedEncodingException; 025import java.sql.Blob; 026import java.sql.Clob; 027import java.sql.PreparedStatement; 028import java.sql.ResultSet; 029import java.sql.SQLException; 030 031import org.apache.commons.logging.Log; 032import org.apache.commons.logging.LogFactory; 033 034/** 035 * Default implementation of the {@link LobHandler} interface. 036 * Invokes the direct accessor methods that {@code java.sql.ResultSet} 037 * and {@code java.sql.PreparedStatement} offer. 038 * 039 * <p>By default, incoming streams are going to be passed to the appropriate 040 * {@code setBinary/Ascii/CharacterStream} method on the JDBC driver's 041 * {@link PreparedStatement}. If the specified content length is negative, 042 * this handler will use the JDBC 4.0 variants of the set-stream methods 043 * without a length parameter; otherwise, it will pass the specified length 044 * on to the driver. 045 * 046 * <p>This LobHandler should work for any JDBC driver that is JDBC compliant 047 * in terms of the spec's suggestions regarding simple BLOB and CLOB handling. 048 * This does not apply to Oracle 9i's drivers at all; as of Oracle 10g, 049 * it does work but may still come with LOB size limitations. Consider using 050 * recent Oracle drivers even when working against an older database server. 051 * See the {@link LobHandler} javadoc for the full set of recommendations. 052 * 053 * <p>Some JDBC drivers require values with a BLOB/CLOB target column to be 054 * explicitly set through the JDBC {@code setBlob} / {@code setClob} API: 055 * for example, PostgreSQL's driver. Switch the {@link #setWrapAsLob "wrapAsLob"} 056 * property to "true" when operating against such a driver. 057 * 058 * <p>On JDBC 4.0, this LobHandler also supports streaming the BLOB/CLOB content 059 * via the {@code setBlob} / {@code setClob} variants that take a stream 060 * argument directly. Consider switching the {@link #setStreamAsLob "streamAsLob"} 061 * property to "true" when operating against a fully compliant JDBC 4.0 driver. 062 * 063 * <p>Finally, primarily as a direct equivalent to {@link OracleLobHandler}, 064 * this LobHandler also supports the creation of temporary BLOB/CLOB objects. 065 * Consider switching the {@link #setCreateTemporaryLob "createTemporaryLob"} 066 * property to "true" when "streamAsLob" happens to run into LOB size limitations. 067 * 068 * <p>See the {@link LobHandler} interface javadoc for a summary of recommendations. 069 * 070 * @author Juergen Hoeller 071 * @since 04.12.2003 072 * @see java.sql.ResultSet#getBytes 073 * @see java.sql.ResultSet#getBinaryStream 074 * @see java.sql.ResultSet#getString 075 * @see java.sql.ResultSet#getAsciiStream 076 * @see java.sql.ResultSet#getCharacterStream 077 * @see java.sql.PreparedStatement#setBytes 078 * @see java.sql.PreparedStatement#setBinaryStream 079 * @see java.sql.PreparedStatement#setString 080 * @see java.sql.PreparedStatement#setAsciiStream 081 * @see java.sql.PreparedStatement#setCharacterStream 082 */ 083public class DefaultLobHandler extends AbstractLobHandler { 084 085 protected final Log logger = LogFactory.getLog(getClass()); 086 087 private boolean wrapAsLob = false; 088 089 private boolean streamAsLob = false; 090 091 private boolean createTemporaryLob = false; 092 093 094 /** 095 * Specify whether to submit a byte array / String to the JDBC driver 096 * wrapped in a JDBC Blob / Clob object, using the JDBC {@code setBlob} / 097 * {@code setClob} method with a Blob / Clob argument. 098 * <p>Default is "false", using the common JDBC 2.0 {@code setBinaryStream} 099 * / {@code setCharacterStream} method for setting the content. Switch this 100 * to "true" for explicit Blob / Clob wrapping against JDBC drivers that 101 * are known to require such wrapping (e.g. PostgreSQL's for access to OID 102 * columns, whereas BYTEA columns need to be accessed the standard way). 103 * <p>This setting affects byte array / String arguments as well as stream 104 * arguments, unless {@link #setStreamAsLob "streamAsLob"} overrides this 105 * handling to use JDBC 4.0's new explicit streaming support (if available). 106 * @see java.sql.PreparedStatement#setBlob(int, java.sql.Blob) 107 * @see java.sql.PreparedStatement#setClob(int, java.sql.Clob) 108 */ 109 public void setWrapAsLob(boolean wrapAsLob) { 110 this.wrapAsLob = wrapAsLob; 111 } 112 113 /** 114 * Specify whether to submit a binary stream / character stream to the JDBC 115 * driver as explicit LOB content, using the JDBC 4.0 {@code setBlob} / 116 * {@code setClob} method with a stream argument. 117 * <p>Default is "false", using the common JDBC 2.0 {@code setBinaryStream} 118 * / {@code setCharacterStream} method for setting the content. 119 * Switch this to "true" for explicit JDBC 4.0 streaming, provided that your 120 * JDBC driver actually supports those JDBC 4.0 operations (e.g. Derby's). 121 * <p>This setting affects stream arguments as well as byte array / String 122 * arguments, requiring JDBC 4.0 support. For supporting LOB content against 123 * JDBC 3.0, check out the {@link #setWrapAsLob "wrapAsLob"} setting. 124 * @see java.sql.PreparedStatement#setBlob(int, java.io.InputStream, long) 125 * @see java.sql.PreparedStatement#setClob(int, java.io.Reader, long) 126 */ 127 public void setStreamAsLob(boolean streamAsLob) { 128 this.streamAsLob = streamAsLob; 129 } 130 131 /** 132 * Specify whether to copy a byte array / String into a temporary JDBC 133 * Blob / Clob object created through the JDBC 4.0 {@code createBlob} / 134 * {@code createClob} methods. 135 * <p>Default is "false", using the common JDBC 2.0 {@code setBinaryStream} 136 * / {@code setCharacterStream} method for setting the content. Switch this 137 * to "true" for explicit Blob / Clob creation using JDBC 4.0. 138 * <p>This setting affects stream arguments as well as byte array / String 139 * arguments, requiring JDBC 4.0 support. For supporting LOB content against 140 * JDBC 3.0, check out the {@link #setWrapAsLob "wrapAsLob"} setting. 141 * @see java.sql.Connection#createBlob() 142 * @see java.sql.Connection#createClob() 143 */ 144 public void setCreateTemporaryLob(boolean createTemporaryLob) { 145 this.createTemporaryLob = createTemporaryLob; 146 } 147 148 149 @Override 150 public byte[] getBlobAsBytes(ResultSet rs, int columnIndex) throws SQLException { 151 logger.debug("Returning BLOB as bytes"); 152 if (this.wrapAsLob) { 153 Blob blob = rs.getBlob(columnIndex); 154 return blob.getBytes(1, (int) blob.length()); 155 } 156 else { 157 return rs.getBytes(columnIndex); 158 } 159 } 160 161 @Override 162 public InputStream getBlobAsBinaryStream(ResultSet rs, int columnIndex) throws SQLException { 163 logger.debug("Returning BLOB as binary stream"); 164 if (this.wrapAsLob) { 165 Blob blob = rs.getBlob(columnIndex); 166 return blob.getBinaryStream(); 167 } 168 else { 169 return rs.getBinaryStream(columnIndex); 170 } 171 } 172 173 @Override 174 public String getClobAsString(ResultSet rs, int columnIndex) throws SQLException { 175 logger.debug("Returning CLOB as string"); 176 if (this.wrapAsLob) { 177 Clob clob = rs.getClob(columnIndex); 178 return clob.getSubString(1, (int) clob.length()); 179 } 180 else { 181 return rs.getString(columnIndex); 182 } 183 } 184 185 @Override 186 public InputStream getClobAsAsciiStream(ResultSet rs, int columnIndex) throws SQLException { 187 logger.debug("Returning CLOB as ASCII stream"); 188 if (this.wrapAsLob) { 189 Clob clob = rs.getClob(columnIndex); 190 return clob.getAsciiStream(); 191 } 192 else { 193 return rs.getAsciiStream(columnIndex); 194 } 195 } 196 197 @Override 198 public Reader getClobAsCharacterStream(ResultSet rs, int columnIndex) throws SQLException { 199 logger.debug("Returning CLOB as character stream"); 200 if (this.wrapAsLob) { 201 Clob clob = rs.getClob(columnIndex); 202 return clob.getCharacterStream(); 203 } 204 else { 205 return rs.getCharacterStream(columnIndex); 206 } 207 } 208 209 @Override 210 public LobCreator getLobCreator() { 211 return (this.createTemporaryLob ? new TemporaryLobCreator() : new DefaultLobCreator()); 212 } 213 214 215 /** 216 * Default LobCreator implementation as an inner class. 217 * Can be subclassed in DefaultLobHandler extensions. 218 */ 219 protected class DefaultLobCreator implements LobCreator { 220 221 @Override 222 public void setBlobAsBytes(PreparedStatement ps, int paramIndex, byte[] content) 223 throws SQLException { 224 225 if (streamAsLob) { 226 if (content != null) { 227 ps.setBlob(paramIndex, new ByteArrayInputStream(content), content.length); 228 } 229 else { 230 ps.setBlob(paramIndex, (Blob) null); 231 } 232 } 233 else if (wrapAsLob) { 234 if (content != null) { 235 ps.setBlob(paramIndex, new PassThroughBlob(content)); 236 } 237 else { 238 ps.setBlob(paramIndex, (Blob) null); 239 } 240 } 241 else { 242 ps.setBytes(paramIndex, content); 243 } 244 if (logger.isDebugEnabled()) { 245 logger.debug(content != null ? "Set bytes for BLOB with length " + content.length : 246 "Set BLOB to null"); 247 } 248 } 249 250 @Override 251 public void setBlobAsBinaryStream( 252 PreparedStatement ps, int paramIndex, InputStream binaryStream, int contentLength) 253 throws SQLException { 254 255 if (streamAsLob) { 256 if (binaryStream != null) { 257 if (contentLength >= 0) { 258 ps.setBlob(paramIndex, binaryStream, contentLength); 259 } 260 else { 261 ps.setBlob(paramIndex, binaryStream); 262 } 263 } 264 else { 265 ps.setBlob(paramIndex, (Blob) null); 266 } 267 } 268 else if (wrapAsLob) { 269 if (binaryStream != null) { 270 ps.setBlob(paramIndex, new PassThroughBlob(binaryStream, contentLength)); 271 } 272 else { 273 ps.setBlob(paramIndex, (Blob) null); 274 } 275 } 276 else if (contentLength >= 0) { 277 ps.setBinaryStream(paramIndex, binaryStream, contentLength); 278 } 279 else { 280 ps.setBinaryStream(paramIndex, binaryStream); 281 } 282 if (logger.isDebugEnabled()) { 283 logger.debug(binaryStream != null ? "Set binary stream for BLOB with length " + contentLength : 284 "Set BLOB to null"); 285 } 286 } 287 288 @Override 289 public void setClobAsString(PreparedStatement ps, int paramIndex, String content) 290 throws SQLException { 291 292 if (streamAsLob) { 293 if (content != null) { 294 ps.setClob(paramIndex, new StringReader(content), content.length()); 295 } 296 else { 297 ps.setClob(paramIndex, (Clob) null); 298 } 299 } 300 else if (wrapAsLob) { 301 if (content != null) { 302 ps.setClob(paramIndex, new PassThroughClob(content)); 303 } 304 else { 305 ps.setClob(paramIndex, (Clob) null); 306 } 307 } 308 else { 309 ps.setString(paramIndex, content); 310 } 311 if (logger.isDebugEnabled()) { 312 logger.debug(content != null ? "Set string for CLOB with length " + content.length() : 313 "Set CLOB to null"); 314 } 315 } 316 317 @Override 318 public void setClobAsAsciiStream( 319 PreparedStatement ps, int paramIndex, InputStream asciiStream, int contentLength) 320 throws SQLException { 321 322 if (streamAsLob) { 323 if (asciiStream != null) { 324 try { 325 Reader reader = new InputStreamReader(asciiStream, "US-ASCII"); 326 if (contentLength >= 0) { 327 ps.setClob(paramIndex, reader, contentLength); 328 } 329 else { 330 ps.setClob(paramIndex, reader); 331 } 332 } 333 catch (UnsupportedEncodingException ex) { 334 throw new SQLException("US-ASCII encoding not supported: " + ex); 335 } 336 } 337 else { 338 ps.setClob(paramIndex, (Clob) null); 339 } 340 } 341 else if (wrapAsLob) { 342 if (asciiStream != null) { 343 ps.setClob(paramIndex, new PassThroughClob(asciiStream, contentLength)); 344 } 345 else { 346 ps.setClob(paramIndex, (Clob) null); 347 } 348 } 349 else if (contentLength >= 0) { 350 ps.setAsciiStream(paramIndex, asciiStream, contentLength); 351 } 352 else { 353 ps.setAsciiStream(paramIndex, asciiStream); 354 } 355 if (logger.isDebugEnabled()) { 356 logger.debug(asciiStream != null ? "Set ASCII stream for CLOB with length " + contentLength : 357 "Set CLOB to null"); 358 } 359 } 360 361 @Override 362 public void setClobAsCharacterStream( 363 PreparedStatement ps, int paramIndex, Reader characterStream, int contentLength) 364 throws SQLException { 365 366 if (streamAsLob) { 367 if (characterStream != null) { 368 if (contentLength >= 0) { 369 ps.setClob(paramIndex, characterStream, contentLength); 370 } 371 else { 372 ps.setClob(paramIndex, characterStream); 373 } 374 } 375 else { 376 ps.setClob(paramIndex, (Clob) null); 377 } 378 } 379 else if (wrapAsLob) { 380 if (characterStream != null) { 381 ps.setClob(paramIndex, new PassThroughClob(characterStream, contentLength)); 382 } 383 else { 384 ps.setClob(paramIndex, (Clob) null); 385 } 386 } 387 else if (contentLength >= 0) { 388 ps.setCharacterStream(paramIndex, characterStream, contentLength); 389 } 390 else { 391 ps.setCharacterStream(paramIndex, characterStream); 392 } 393 if (logger.isDebugEnabled()) { 394 logger.debug(characterStream != null ? "Set character stream for CLOB with length " + contentLength : 395 "Set CLOB to null"); 396 } 397 } 398 399 @Override 400 public void close() { 401 // nothing to do when not creating temporary LOBs 402 } 403 } 404 405}