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