001 package com.alexkasko.springjdbc.iterable; 002 003 import org.apache.commons.logging.Log; 004 import org.apache.commons.logging.LogFactory; 005 import org.springframework.dao.DataAccessException; 006 import org.springframework.jdbc.core.*; 007 import org.springframework.jdbc.datasource.DataSourceUtils; 008 import org.springframework.jdbc.support.JdbcUtils; 009 import org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor; 010 import org.springframework.util.Assert; 011 012 import javax.sql.DataSource; 013 import java.sql.*; 014 import java.util.Map; 015 016 /** 017 * {@code JdbcTemplate} extension. All methods, that return {@code List} 018 * mirrored with {@code queryForIter} methods that return {@link CloseableIterator}. 019 * 020 * @author alexkasko 021 * Date: 11/7/12 022 */ 023 public class IterableJdbcTemplate extends JdbcTemplate implements IterableJdbcOperations { 024 private static final Log logger = LogFactory.getLog(IterableJdbcTemplate.class); 025 026 /** 027 * Constructor 028 * 029 * @param dataSource data source 030 */ 031 public IterableJdbcTemplate(DataSource dataSource) { 032 super(dataSource); 033 } 034 035 /** 036 * Constructor, takes {@code fetchSize} 037 * 038 * @param dataSource data source 039 * @param fetchSize <a href=http://static.springsource.org/spring/docs/3.0.x/api/org/springframework/jdbc/core/JdbcTemplate.html#setFetchSize%28int%29>fetchSize</a> property value 040 */ 041 public IterableJdbcTemplate(DataSource dataSource, int fetchSize) { 042 this(dataSource); 043 setFetchSize(fetchSize); 044 } 045 046 /** 047 * Static method to use in finally methods for closing 048 * {@link CloseableIterator}s. Writes warning into log on exception. 049 * Since 1.2 <a href="http://commons.apache.org/proper/commons-io//javadocs/api-2.4/org/apache/commons/io/IOUtils.html#closeQuietly%28java.io.Closeable%29">IOUtils.closeQuietly()</a> 050 * may be used instead this one. 051 * 052 * @param iter iterator to close 053 */ 054 @Deprecated // use IOUtils.closeQuietly() instead 055 public static void closeQuetly(CloseableIterator<?> iter) { 056 try { 057 if (iter != null) { 058 iter.close(); 059 } 060 } catch (Exception e) { 061 logger.warn("Error on closing iterator: [" + iter + "]", e); 062 } 063 } 064 065 /** 066 * {@inheritDoc} 067 */ 068 @Override 069 public <T> CloseableIterator<T> queryForIter(PreparedStatementCreator psc, RowMapper<T> rowMapper) throws DataAccessException { 070 return queryForIter(psc, null, rowMapper); 071 } 072 073 /** 074 * {@inheritDoc} 075 */ 076 @Override 077 // see {@code JdbcTemplate#query(org.springframework.jdbc.core.PreparedStatementCreator, org.springframework.jdbc.core.PreparedStatementSetter, org.springframework.jdbc.core.ResultSetExtractor)} 078 // see {@code JdbcTemplate#execute(org.springframework.jdbc.core.PreparedStatementCreator, org.springframework.jdbc.core.PreparedStatementCallback)} 079 // see {@code JdbcTemplate#query(org.springframework.jdbc.core.PreparedStatementCreator, org.springframework.jdbc.core.PreparedStatementSetter, org.springframework.jdbc.core.ResultSetExtractor)} 080 public <T> CloseableIterator<T> queryForIter(PreparedStatementCreator psc, PreparedStatementSetter pss, 081 RowMapper<T> rowMapper) throws DataAccessException { 082 Assert.notNull(psc, "PreparedStatementCreator must not be null"); 083 Assert.notNull(rowMapper, "RowMapper must not be null"); 084 if(logger.isDebugEnabled()) { 085 String sql = getSql(psc); 086 logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : "")); 087 } 088 DataSource ds = getDataSource(); 089 Connection con = DataSourceUtils.getConnection(ds); 090 PreparedStatement ps = null; 091 ResultSet rs = null; 092 try { 093 Connection conToUse = con; 094 NativeJdbcExtractor nje = getNativeJdbcExtractor(); 095 if(nje != null && nje.isNativeConnectionNecessaryForNativePreparedStatements()) { 096 conToUse = nje.getNativeConnection(con); 097 } 098 ps = psc.createPreparedStatement(conToUse); 099 applyStatementSettings(ps); 100 PreparedStatement psToUse = ps; 101 if(nje != null) { 102 psToUse = nje.getNativePreparedStatement(ps); 103 } 104 if(pss != null) { 105 pss.setValues(psToUse); 106 } 107 rs = psToUse.executeQuery(); 108 ResultSet rsToUse = rs; 109 if(nje != null) { 110 rsToUse = nje.getNativeResultSet(rs); 111 } 112 // warnings are handled after query execution but before data access 113 handleWarnings(ps); 114 return new PreparedStatementCloseableIterator<T>(ds, con, psc, pss, ps, rs, rsToUse, rowMapper); 115 } catch(SQLException ex) { 116 // Release Connection early, to avoid potential connection pool deadlock 117 // in the case when the exception translator hasn't been initialized yet. 118 if(psc instanceof ParameterDisposer) { 119 ((ParameterDisposer) psc).cleanupParameters(); 120 } 121 String sql = getSql(psc); 122 psc = null; 123 JdbcUtils.closeResultSet(rs); 124 rs = null; 125 JdbcUtils.closeStatement(ps); 126 ps = null; 127 DataSourceUtils.releaseConnection(con, getDataSource()); 128 con = null; 129 throw getExceptionTranslator().translate("PreparedStatementCallback", sql, ex); 130 } 131 // resources will be closed in iterator 132 // finally { 133 // if(psc instanceof ParameterDisposer) { 134 // ((ParameterDisposer) psc).cleanupParameters(); 135 // } 136 // JdbcUtils.closeStatement(ps); 137 // DataSourceUtils.releaseConnection(con, getDataSource()); 138 // } 139 } 140 141 /** 142 * {@inheritDoc} 143 */ 144 @Override 145 public <T> CloseableIterator<T> queryForIter(String sql, RowMapper<T> rowMapper) throws DataAccessException { 146 Assert.hasText(sql, "Provided sql query is blank"); 147 Assert.notNull(rowMapper, "RowMapper must not be null"); 148 DataSource ds = getDataSource(); 149 Connection con = DataSourceUtils.getConnection(ds); 150 Statement stmt = null; 151 ResultSet rs = null; 152 try { 153 Connection conToUse = con; 154 NativeJdbcExtractor nje = getNativeJdbcExtractor(); 155 if(nje != null && nje.isNativeConnectionNecessaryForNativeStatements()) { 156 conToUse = nje.getNativeConnection(con); 157 } 158 stmt = conToUse.createStatement(); 159 applyStatementSettings(stmt); 160 Statement stmtToUse = stmt; 161 if(nje != null) { 162 stmtToUse = nje.getNativeStatement(stmt); 163 } 164 rs = stmtToUse.executeQuery(sql); 165 ResultSet rsToUse = rs; 166 if(nje != null) { 167 rsToUse = nje.getNativeResultSet(rs); 168 } 169 // warnings are handled after query execution but before data access 170 handleWarnings(stmt); 171 return new StatementCloseableIterator<T>(ds, con, stmt, rs, rsToUse, rowMapper); 172 } catch(SQLException ex) { 173 JdbcUtils.closeResultSet(rs); 174 rs = null; 175 // Release Connection early, to avoid potential connection pool deadlock 176 // in the case when the exception translator hasn't been initialized yet. 177 JdbcUtils.closeStatement(stmt); 178 stmt = null; 179 DataSourceUtils.releaseConnection(con, getDataSource()); 180 con = null; 181 throw getExceptionTranslator().translate("StatementCallback", getSql(sql), ex); 182 } 183 // resources will be closed in iterator 184 // finally { 185 // JdbcUtils.closeStatement(stmt); 186 // DataSourceUtils.releaseConnection(con, getDataSource()); 187 // } 188 } 189 190 /** 191 * {@inheritDoc} 192 */ 193 @Override 194 public <T> CloseableIterator<T> queryForIter(String sql, Class<T> elementType) throws DataAccessException { 195 return queryForIter(sql, getSingleColumnRowMapper(elementType)); 196 } 197 198 /** 199 * {@inheritDoc} 200 */ 201 @Override 202 public CloseableIterator<Map<String, Object>> queryForIter(String sql) throws DataAccessException { 203 return queryForIter(sql, getColumnMapRowMapper()); 204 } 205 206 /** 207 * {@inheritDoc} 208 */ 209 @Override 210 public <T> CloseableIterator<T> queryForIter(String sql, PreparedStatementSetter pss, RowMapper<T> rowMapper) throws DataAccessException { 211 return queryForIter(new SimplePreparedStatementCreator(sql), pss, rowMapper); 212 } 213 214 /** 215 * {@inheritDoc} 216 */ 217 @Override 218 public <T> CloseableIterator<T> queryForIter(String sql, Object[] args, int[] argTypes, RowMapper<T> rowMapper) throws DataAccessException { 219 return queryForIter(sql, newArgTypePreparedStatementSetter(args, argTypes), rowMapper); 220 } 221 222 /** 223 * {@inheritDoc} 224 */ 225 @Override 226 public <T> CloseableIterator<T> queryForIter(String sql, Object[] args, RowMapper<T> rowMapper) throws DataAccessException { 227 return queryForIter(sql, newArgPreparedStatementSetter(args), rowMapper); 228 } 229 230 /** 231 * {@inheritDoc} 232 */ 233 @Override 234 public <T> CloseableIterator<T> queryForIter(String sql, RowMapper<T> rowMapper, Object... args) throws DataAccessException { 235 return queryForIter(sql, newArgPreparedStatementSetter(args), rowMapper); 236 } 237 238 /** 239 * {@inheritDoc} 240 */ 241 @Override 242 public <T> CloseableIterator<T> queryForIter(String sql, Object[] args, int[] argTypes, Class<T> elementType) throws DataAccessException { 243 return queryForIter(sql, args, argTypes, getSingleColumnRowMapper(elementType)); 244 } 245 246 /** 247 * {@inheritDoc} 248 */ 249 @Override 250 public <T> CloseableIterator<T> queryForIter(String sql, Object[] args, Class<T> elementType) throws DataAccessException { 251 return queryForIter(sql, args, getSingleColumnRowMapper(elementType)); 252 } 253 254 /** 255 * {@inheritDoc} 256 */ 257 @Override 258 public <T> CloseableIterator<T> queryForIter(String sql, Class<T> elementType, Object... args) throws DataAccessException { 259 return queryForIter(sql, args, getSingleColumnRowMapper(elementType)); 260 } 261 262 /** 263 * {@inheritDoc} 264 */ 265 @Override 266 public CloseableIterator<Map<String, Object>> queryForIter(String sql, Object[] args, int[] argTypes) throws DataAccessException { 267 return queryForIter(sql, args, argTypes, getColumnMapRowMapper()); 268 } 269 270 /** 271 * {@inheritDoc} 272 */ 273 @Override 274 public CloseableIterator<Map<String, Object>> queryForIter(String sql, Object... args) throws DataAccessException { 275 return queryForIter(sql, args, getColumnMapRowMapper()); 276 } 277 278 /** 279 * Determine SQL from potential provider object. 280 * Borrowed from {@code JdbcTemplate} 281 * 282 * @param sqlProvider object that's potentially a SqlProvider 283 * @return the SQL string, or <code>null</code> 284 * @see SqlProvider 285 */ 286 private static String getSql(Object sqlProvider) { 287 if(sqlProvider instanceof SqlProvider) { 288 return ((SqlProvider) sqlProvider).getSql(); 289 } else { 290 return null; 291 } 292 } 293 294 /** 295 * Simple adapter for PreparedStatementCreator, allowing to use a plain SQL statement. 296 * Borrowed from {@code JdbcTemplate} 297 */ 298 private static class SimplePreparedStatementCreator implements PreparedStatementCreator, SqlProvider { 299 private final String sql; 300 public SimplePreparedStatementCreator(String sql) { 301 Assert.notNull(sql, "SQL must not be null"); 302 this.sql = sql; 303 } 304 public PreparedStatement createPreparedStatement(Connection con) throws SQLException { 305 return con.prepareStatement(this.sql); 306 } 307 public String getSql() { 308 return this.sql; 309 } 310 } 311 }