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