001/* 002 * Copyright 2002-2018 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.CallableStatement; 020import java.sql.Connection; 021import java.sql.ResultSet; 022import java.sql.SQLException; 023import java.util.HashMap; 024import java.util.LinkedList; 025import java.util.List; 026import java.util.Map; 027 028import org.springframework.dao.InvalidDataAccessApiUsageException; 029import org.springframework.lang.Nullable; 030 031/** 032 * Helper class that efficiently creates multiple {@link CallableStatementCreator} 033 * objects with different parameters based on an SQL statement and a single 034 * set of parameter declarations. 035 * 036 * @author Rod Johnson 037 * @author Thomas Risberg 038 * @author Juergen Hoeller 039 */ 040public class CallableStatementCreatorFactory { 041 042 /** The SQL call string, which won't change when the parameters change. */ 043 private final String callString; 044 045 /** List of SqlParameter objects. May not be {@code null}. */ 046 private final List<SqlParameter> declaredParameters; 047 048 private int resultSetType = ResultSet.TYPE_FORWARD_ONLY; 049 050 private boolean updatableResults = false; 051 052 053 /** 054 * Create a new factory. Will need to add parameters via the 055 * {@link #addParameter} method or have no parameters. 056 * @param callString the SQL call string 057 */ 058 public CallableStatementCreatorFactory(String callString) { 059 this.callString = callString; 060 this.declaredParameters = new LinkedList<>(); 061 } 062 063 /** 064 * Create a new factory with the given SQL and the given parameters. 065 * @param callString the SQL call string 066 * @param declaredParameters list of {@link SqlParameter} objects 067 */ 068 public CallableStatementCreatorFactory(String callString, List<SqlParameter> declaredParameters) { 069 this.callString = callString; 070 this.declaredParameters = declaredParameters; 071 } 072 073 074 /** 075 * Return the SQL call string. 076 * @since 5.1.3 077 */ 078 public final String getCallString() { 079 return this.callString; 080 } 081 082 /** 083 * Add a new declared parameter. 084 * <p>Order of parameter addition is significant. 085 * @param param the parameter to add to the list of declared parameters 086 */ 087 public void addParameter(SqlParameter param) { 088 this.declaredParameters.add(param); 089 } 090 091 /** 092 * Set whether to use prepared statements that return a specific type of ResultSet. 093 * specific type of ResultSet. 094 * @param resultSetType the ResultSet type 095 * @see java.sql.ResultSet#TYPE_FORWARD_ONLY 096 * @see java.sql.ResultSet#TYPE_SCROLL_INSENSITIVE 097 * @see java.sql.ResultSet#TYPE_SCROLL_SENSITIVE 098 */ 099 public void setResultSetType(int resultSetType) { 100 this.resultSetType = resultSetType; 101 } 102 103 /** 104 * Set whether to use prepared statements capable of returning updatable ResultSets. 105 */ 106 public void setUpdatableResults(boolean updatableResults) { 107 this.updatableResults = updatableResults; 108 } 109 110 111 /** 112 * Return a new CallableStatementCreator instance given this parameters. 113 * @param params list of parameters (may be {@code null}) 114 */ 115 public CallableStatementCreator newCallableStatementCreator(@Nullable Map<String, ?> params) { 116 return new CallableStatementCreatorImpl(params != null ? params : new HashMap<>()); 117 } 118 119 /** 120 * Return a new CallableStatementCreator instance given this parameter mapper. 121 * @param inParamMapper the ParameterMapper implementation that will return a Map of parameters 122 */ 123 public CallableStatementCreator newCallableStatementCreator(ParameterMapper inParamMapper) { 124 return new CallableStatementCreatorImpl(inParamMapper); 125 } 126 127 128 /** 129 * CallableStatementCreator implementation returned by this class. 130 */ 131 private class CallableStatementCreatorImpl implements CallableStatementCreator, SqlProvider, ParameterDisposer { 132 133 @Nullable 134 private ParameterMapper inParameterMapper; 135 136 @Nullable 137 private Map<String, ?> inParameters; 138 139 /** 140 * Create a new CallableStatementCreatorImpl. 141 * @param inParamMapper the ParameterMapper implementation for mapping input parameters 142 */ 143 public CallableStatementCreatorImpl(ParameterMapper inParamMapper) { 144 this.inParameterMapper = inParamMapper; 145 } 146 147 /** 148 * Create a new CallableStatementCreatorImpl. 149 * @param inParams list of SqlParameter objects 150 */ 151 public CallableStatementCreatorImpl(Map<String, ?> inParams) { 152 this.inParameters = inParams; 153 } 154 155 @Override 156 public CallableStatement createCallableStatement(Connection con) throws SQLException { 157 // If we were given a ParameterMapper, we must let the mapper do its thing to create the Map. 158 if (this.inParameterMapper != null) { 159 this.inParameters = this.inParameterMapper.createMap(con); 160 } 161 else { 162 if (this.inParameters == null) { 163 throw new InvalidDataAccessApiUsageException( 164 "A ParameterMapper or a Map of parameters must be provided"); 165 } 166 } 167 168 CallableStatement cs = null; 169 if (resultSetType == ResultSet.TYPE_FORWARD_ONLY && !updatableResults) { 170 cs = con.prepareCall(callString); 171 } 172 else { 173 cs = con.prepareCall(callString, resultSetType, 174 updatableResults ? ResultSet.CONCUR_UPDATABLE : ResultSet.CONCUR_READ_ONLY); 175 } 176 177 int sqlColIndx = 1; 178 for (SqlParameter declaredParam : declaredParameters) { 179 if (!declaredParam.isResultsParameter()) { 180 // So, it's a call parameter - part of the call string. 181 // Get the value - it may still be null. 182 Object inValue = this.inParameters.get(declaredParam.getName()); 183 if (declaredParam instanceof ResultSetSupportingSqlParameter) { 184 // It's an output parameter: SqlReturnResultSet parameters already excluded. 185 // It need not (but may be) supplied by the caller. 186 if (declaredParam instanceof SqlOutParameter) { 187 if (declaredParam.getTypeName() != null) { 188 cs.registerOutParameter(sqlColIndx, declaredParam.getSqlType(), declaredParam.getTypeName()); 189 } 190 else { 191 if (declaredParam.getScale() != null) { 192 cs.registerOutParameter(sqlColIndx, declaredParam.getSqlType(), declaredParam.getScale()); 193 } 194 else { 195 cs.registerOutParameter(sqlColIndx, declaredParam.getSqlType()); 196 } 197 } 198 if (declaredParam.isInputValueProvided()) { 199 StatementCreatorUtils.setParameterValue(cs, sqlColIndx, declaredParam, inValue); 200 } 201 } 202 } 203 else { 204 // It's an input parameter; must be supplied by the caller. 205 if (!this.inParameters.containsKey(declaredParam.getName())) { 206 throw new InvalidDataAccessApiUsageException( 207 "Required input parameter '" + declaredParam.getName() + "' is missing"); 208 } 209 StatementCreatorUtils.setParameterValue(cs, sqlColIndx, declaredParam, inValue); 210 } 211 sqlColIndx++; 212 } 213 } 214 215 return cs; 216 } 217 218 @Override 219 public String getSql() { 220 return callString; 221 } 222 223 @Override 224 public void cleanupParameters() { 225 if (this.inParameters != null) { 226 StatementCreatorUtils.cleanupParameters(this.inParameters.values()); 227 } 228 } 229 230 @Override 231 public String toString() { 232 return "CallableStatementCreator: sql=[" + callString + "]; parameters=" + this.inParameters; 233 } 234 } 235 236}