001/* 002 * Copyright 2002-2020 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.core; 018 019import java.sql.Connection; 020import java.sql.PreparedStatement; 021import java.sql.ResultSet; 022import java.sql.SQLException; 023import java.sql.Types; 024import java.util.Arrays; 025import java.util.Collection; 026import java.util.Collections; 027import java.util.HashSet; 028import java.util.LinkedList; 029import java.util.List; 030import java.util.Set; 031 032import org.springframework.dao.InvalidDataAccessApiUsageException; 033import org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor; 034 035/** 036 * Helper class that efficiently creates multiple {@link PreparedStatementCreator} 037 * objects with different parameters based on a SQL statement and a single 038 * set of parameter declarations. 039 * 040 * @author Rod Johnson 041 * @author Thomas Risberg 042 * @author Juergen Hoeller 043 */ 044public class PreparedStatementCreatorFactory { 045 046 /** The SQL, which won't change when the parameters change */ 047 private final String sql; 048 049 /** List of SqlParameter objects (may not be {@code null}) */ 050 private final List<SqlParameter> declaredParameters; 051 052 private int resultSetType = ResultSet.TYPE_FORWARD_ONLY; 053 054 private boolean updatableResults = false; 055 056 private boolean returnGeneratedKeys = false; 057 058 private String[] generatedKeysColumnNames; 059 060 private NativeJdbcExtractor nativeJdbcExtractor; 061 062 063 /** 064 * Create a new factory. Will need to add parameters via the 065 * {@link #addParameter} method or have no parameters. 066 * @param sql the SQL statement to execute 067 */ 068 public PreparedStatementCreatorFactory(String sql) { 069 this.sql = sql; 070 this.declaredParameters = new LinkedList<SqlParameter>(); 071 } 072 073 /** 074 * Create a new factory with the given SQL and JDBC types. 075 * @param sql the SQL statement to execute 076 * @param types int array of JDBC types 077 */ 078 public PreparedStatementCreatorFactory(String sql, int... types) { 079 this.sql = sql; 080 this.declaredParameters = SqlParameter.sqlTypesToAnonymousParameterList(types); 081 } 082 083 /** 084 * Create a new factory with the given SQL and parameters. 085 * @param sql the SQL statement to execute 086 * @param declaredParameters list of {@link SqlParameter} objects 087 */ 088 public PreparedStatementCreatorFactory(String sql, List<SqlParameter> declaredParameters) { 089 this.sql = sql; 090 this.declaredParameters = declaredParameters; 091 } 092 093 094 /** 095 * Add a new declared parameter. 096 * <p>Order of parameter addition is significant. 097 * @param param the parameter to add to the list of declared parameters 098 */ 099 public void addParameter(SqlParameter param) { 100 this.declaredParameters.add(param); 101 } 102 103 /** 104 * Set whether to use prepared statements that return a specific type of ResultSet. 105 * @param resultSetType the ResultSet type 106 * @see java.sql.ResultSet#TYPE_FORWARD_ONLY 107 * @see java.sql.ResultSet#TYPE_SCROLL_INSENSITIVE 108 * @see java.sql.ResultSet#TYPE_SCROLL_SENSITIVE 109 */ 110 public void setResultSetType(int resultSetType) { 111 this.resultSetType = resultSetType; 112 } 113 114 /** 115 * Set whether to use prepared statements capable of returning updatable ResultSets. 116 */ 117 public void setUpdatableResults(boolean updatableResults) { 118 this.updatableResults = updatableResults; 119 } 120 121 /** 122 * Set whether prepared statements should be capable of returning auto-generated keys. 123 */ 124 public void setReturnGeneratedKeys(boolean returnGeneratedKeys) { 125 this.returnGeneratedKeys = returnGeneratedKeys; 126 } 127 128 /** 129 * Set the column names of the auto-generated keys. 130 */ 131 public void setGeneratedKeysColumnNames(String... names) { 132 this.generatedKeysColumnNames = names; 133 } 134 135 /** 136 * Specify the NativeJdbcExtractor to use for unwrapping PreparedStatements, if any. 137 */ 138 public void setNativeJdbcExtractor(NativeJdbcExtractor nativeJdbcExtractor) { 139 this.nativeJdbcExtractor = nativeJdbcExtractor; 140 } 141 142 143 /** 144 * Return a new PreparedStatementSetter for the given parameters. 145 * @param params list of parameters (may be {@code null}) 146 */ 147 public PreparedStatementSetter newPreparedStatementSetter(List<?> params) { 148 return new PreparedStatementCreatorImpl(params != null ? params : Collections.emptyList()); 149 } 150 151 /** 152 * Return a new PreparedStatementSetter for the given parameters. 153 * @param params the parameter array (may be {@code null}) 154 */ 155 public PreparedStatementSetter newPreparedStatementSetter(Object[] params) { 156 return new PreparedStatementCreatorImpl(params != null ? Arrays.asList(params) : Collections.emptyList()); 157 } 158 159 /** 160 * Return a new PreparedStatementCreator for the given parameters. 161 * @param params list of parameters (may be {@code null}) 162 */ 163 public PreparedStatementCreator newPreparedStatementCreator(List<?> params) { 164 return new PreparedStatementCreatorImpl(params != null ? params : Collections.emptyList()); 165 } 166 167 /** 168 * Return a new PreparedStatementCreator for the given parameters. 169 * @param params the parameter array (may be {@code null}) 170 */ 171 public PreparedStatementCreator newPreparedStatementCreator(Object[] params) { 172 return new PreparedStatementCreatorImpl(params != null ? Arrays.asList(params) : Collections.emptyList()); 173 } 174 175 /** 176 * Return a new PreparedStatementCreator for the given parameters. 177 * @param sqlToUse the actual SQL statement to use (if different from 178 * the factory's, for example because of named parameter expanding) 179 * @param params the parameter array (may be {@code null}) 180 */ 181 public PreparedStatementCreator newPreparedStatementCreator(String sqlToUse, Object[] params) { 182 return new PreparedStatementCreatorImpl( 183 sqlToUse, params != null ? Arrays.asList(params) : Collections.emptyList()); 184 } 185 186 187 /** 188 * PreparedStatementCreator implementation returned by this class. 189 */ 190 private class PreparedStatementCreatorImpl 191 implements PreparedStatementCreator, PreparedStatementSetter, SqlProvider, ParameterDisposer { 192 193 private final String actualSql; 194 195 private final List<?> parameters; 196 197 public PreparedStatementCreatorImpl(List<?> parameters) { 198 this(sql, parameters); 199 } 200 201 public PreparedStatementCreatorImpl(String actualSql, List<?> parameters) { 202 this.actualSql = actualSql; 203 this.parameters = parameters; 204 if (parameters.size() != declaredParameters.size()) { 205 // Account for named parameters being used multiple times 206 Set<String> names = new HashSet<String>(); 207 for (int i = 0; i < parameters.size(); i++) { 208 Object param = parameters.get(i); 209 if (param instanceof SqlParameterValue) { 210 names.add(((SqlParameterValue) param).getName()); 211 } 212 else { 213 names.add("Parameter #" + i); 214 } 215 } 216 if (names.size() != declaredParameters.size()) { 217 throw new InvalidDataAccessApiUsageException( 218 "SQL [" + sql + "]: given " + names.size() + 219 " parameters but expected " + declaredParameters.size()); 220 } 221 } 222 } 223 224 @Override 225 public PreparedStatement createPreparedStatement(Connection con) throws SQLException { 226 PreparedStatement ps; 227 if (generatedKeysColumnNames != null || returnGeneratedKeys) { 228 if (generatedKeysColumnNames != null) { 229 ps = con.prepareStatement(this.actualSql, generatedKeysColumnNames); 230 } 231 else { 232 ps = con.prepareStatement(this.actualSql, PreparedStatement.RETURN_GENERATED_KEYS); 233 } 234 } 235 else if (resultSetType == ResultSet.TYPE_FORWARD_ONLY && !updatableResults) { 236 ps = con.prepareStatement(this.actualSql); 237 } 238 else { 239 ps = con.prepareStatement(this.actualSql, resultSetType, 240 updatableResults ? ResultSet.CONCUR_UPDATABLE : ResultSet.CONCUR_READ_ONLY); 241 } 242 setValues(ps); 243 return ps; 244 } 245 246 @Override 247 public void setValues(PreparedStatement ps) throws SQLException { 248 // Determine PreparedStatement to pass to custom types. 249 PreparedStatement psToUse = ps; 250 if (nativeJdbcExtractor != null) { 251 psToUse = nativeJdbcExtractor.getNativePreparedStatement(ps); 252 } 253 254 // Set arguments: Does nothing if there are no parameters. 255 int sqlColIndx = 1; 256 for (int i = 0; i < this.parameters.size(); i++) { 257 Object in = this.parameters.get(i); 258 SqlParameter declaredParameter; 259 // SqlParameterValue overrides declared parameter meta-data, in particular for 260 // independence from the declared parameter position in case of named parameters. 261 if (in instanceof SqlParameterValue) { 262 SqlParameterValue paramValue = (SqlParameterValue) in; 263 in = paramValue.getValue(); 264 declaredParameter = paramValue; 265 } 266 else { 267 if (declaredParameters.size() <= i) { 268 throw new InvalidDataAccessApiUsageException( 269 "SQL [" + sql + "]: unable to access parameter number " + (i + 1) + 270 " given only " + declaredParameters.size() + " parameters"); 271 272 } 273 declaredParameter = declaredParameters.get(i); 274 } 275 if (in instanceof Collection && declaredParameter.getSqlType() != Types.ARRAY) { 276 Collection<?> entries = (Collection<?>) in; 277 for (Object entry : entries) { 278 if (entry instanceof Object[]) { 279 Object[] valueArray = ((Object[]) entry); 280 for (Object argValue : valueArray) { 281 StatementCreatorUtils.setParameterValue(psToUse, sqlColIndx++, declaredParameter, argValue); 282 } 283 } 284 else { 285 StatementCreatorUtils.setParameterValue(psToUse, sqlColIndx++, declaredParameter, entry); 286 } 287 } 288 } 289 else { 290 StatementCreatorUtils.setParameterValue(psToUse, sqlColIndx++, declaredParameter, in); 291 } 292 } 293 } 294 295 @Override 296 public String getSql() { 297 return sql; 298 } 299 300 @Override 301 public void cleanupParameters() { 302 StatementCreatorUtils.cleanupParameters(this.parameters); 303 } 304 305 @Override 306 public String toString() { 307 return "PreparedStatementCreator: sql=[" + sql + "]; parameters=" + this.parameters; 308 } 309 } 310 311}