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