View Javadoc
1   /*
2    * Copyright 2002-2012 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.jdbc.core;
18  
19  import java.sql.CallableStatement;
20  import java.sql.Connection;
21  import java.sql.ResultSet;
22  import java.sql.SQLException;
23  import java.util.HashMap;
24  import java.util.LinkedList;
25  import java.util.List;
26  import java.util.Map;
27  
28  import org.springframework.dao.InvalidDataAccessApiUsageException;
29  import org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor;
30  
31  /**
32   * Helper class that efficiently creates multiple {@link CallableStatementCreator}
33   * objects with different parameters based on a SQL statement and a single
34   * set of parameter declarations.
35   *
36   * @author Rod Johnson
37   * @author Thomas Risberg
38   * @author Juergen Hoeller
39   */
40  public class CallableStatementCreatorFactory {
41  
42  	/** The SQL call string, which won't change when the parameters change. */
43  	private final String callString;
44  
45  	/** List of SqlParameter objects. May not be {@code null}. */
46  	private final List<SqlParameter> declaredParameters;
47  
48  	private int resultSetType = ResultSet.TYPE_FORWARD_ONLY;
49  
50  	private boolean updatableResults = false;
51  
52  	private NativeJdbcExtractor nativeJdbcExtractor;
53  
54  
55  	/**
56  	 * Create a new factory. Will need to add parameters via the
57  	 * {@link #addParameter} method or have no parameters.
58  	 */
59  	public CallableStatementCreatorFactory(String callString) {
60  		this.callString = callString;
61  		this.declaredParameters = new LinkedList<SqlParameter>();
62  	}
63  
64  	/**
65  	 * Create a new factory with the given SQL and the given parameters.
66  	 * @param callString the SQL call string
67  	 * @param declaredParameters list of {@link SqlParameter} objects
68  	 */
69  	public CallableStatementCreatorFactory(String callString, List<SqlParameter> declaredParameters) {
70  		this.callString = callString;
71  		this.declaredParameters = declaredParameters;
72  	}
73  
74  
75  	/**
76  	 * Add a new declared parameter.
77  	 * <p>Order of parameter addition is significant.
78  	 * @param param the parameter to add to the list of declared parameters
79  	 */
80  	public void addParameter(SqlParameter param) {
81  		this.declaredParameters.add(param);
82  	}
83  
84  	/**
85  	 * Set whether to use prepared statements that return a specific type of ResultSet.
86  	 * specific type of ResultSet.
87  	 * @param resultSetType the ResultSet type
88  	 * @see java.sql.ResultSet#TYPE_FORWARD_ONLY
89  	 * @see java.sql.ResultSet#TYPE_SCROLL_INSENSITIVE
90  	 * @see java.sql.ResultSet#TYPE_SCROLL_SENSITIVE
91  	 */
92  	public void setResultSetType(int resultSetType) {
93  		this.resultSetType = resultSetType;
94  	}
95  
96  	/**
97  	 * Set whether to use prepared statements capable of returning updatable ResultSets.
98  	 */
99  	public void setUpdatableResults(boolean updatableResults) {
100 		this.updatableResults = updatableResults;
101 	}
102 
103 	/**
104 	 * Specify the NativeJdbcExtractor to use for unwrapping CallableStatements, if any.
105 	 */
106 	public void setNativeJdbcExtractor(NativeJdbcExtractor nativeJdbcExtractor) {
107 		this.nativeJdbcExtractor = nativeJdbcExtractor;
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(Map<String, ?> params) {
116 		return new CallableStatementCreatorImpl(params != null ? params : new HashMap<String, Object>());
117 	}
118 
119 	/**
120 	 * Return a new CallableStatementCreator instance given this parameter mapper.
121 	 * @param inParamMapper 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 		private ParameterMapper inParameterMapper;
134 
135 		private Map<String, ?> inParameters;
136 
137 		/**
138 		 * Create a new CallableStatementCreatorImpl.
139 		 * @param inParamMapper ParameterMapper implementation for mapping input parameters
140 		 */
141 		public CallableStatementCreatorImpl(ParameterMapper inParamMapper) {
142 			this.inParameterMapper = inParamMapper;
143 		}
144 
145 		/**
146 		 * Create a new CallableStatementCreatorImpl.
147 		 * @param inParams list of SqlParameter objects
148 		 */
149 		public CallableStatementCreatorImpl(Map<String, ?> inParams) {
150 			this.inParameters = inParams;
151 		}
152 
153 		@Override
154 		public CallableStatement createCallableStatement(Connection con) throws SQLException {
155 			// If we were given a ParameterMapper, we must let the mapper do its thing to create the Map.
156 			if (this.inParameterMapper != null) {
157 				this.inParameters = this.inParameterMapper.createMap(con);
158 			}
159 			else {
160 				if (this.inParameters == null) {
161 					throw new InvalidDataAccessApiUsageException(
162 							"A ParameterMapper or a Map of parameters must be provided");
163 				}
164 			}
165 
166 			CallableStatement cs = null;
167 			if (resultSetType == ResultSet.TYPE_FORWARD_ONLY && !updatableResults) {
168 				cs = con.prepareCall(callString);
169 			}
170 			else {
171 				cs = con.prepareCall(callString, resultSetType,
172 						updatableResults ? ResultSet.CONCUR_UPDATABLE : ResultSet.CONCUR_READ_ONLY);
173 			}
174 
175 			// Determine CallabeStatement to pass to custom types.
176 			CallableStatement csToUse = cs;
177 			if (nativeJdbcExtractor != null) {
178 				csToUse = nativeJdbcExtractor.getNativeCallableStatement(cs);
179 			}
180 
181 			int sqlColIndx = 1;
182 			for (SqlParameter declaredParam : declaredParameters) {
183 				if (!declaredParam.isResultsParameter()) {
184 					// So, it's a call parameter - part of the call string.
185 					// Get the value - it may still be null.
186 					Object inValue = this.inParameters.get(declaredParam.getName());
187 					if (declaredParam instanceof ResultSetSupportingSqlParameter) {
188 						// It's an output parameter: SqlReturnResultSet parameters already excluded.
189 						// It need not (but may be) supplied by the caller.
190 						if (declaredParam instanceof SqlOutParameter) {
191 							if (declaredParam.getTypeName() != null) {
192 								cs.registerOutParameter(sqlColIndx, declaredParam.getSqlType(), declaredParam.getTypeName());
193 							}
194 							else {
195 								if (declaredParam.getScale() != null) {
196 									cs.registerOutParameter(sqlColIndx, declaredParam.getSqlType(), declaredParam.getScale());
197 								}
198 								else {
199 									cs.registerOutParameter(sqlColIndx, declaredParam.getSqlType());
200 								}
201 							}
202 							if (declaredParam.isInputValueProvided()) {
203 								StatementCreatorUtils.setParameterValue(csToUse, sqlColIndx, declaredParam, inValue);
204 							}
205 						}
206 					}
207 					else {
208 						// It's an input parameter; must be supplied by the caller.
209 						if (!this.inParameters.containsKey(declaredParam.getName())) {
210 							throw new InvalidDataAccessApiUsageException(
211 									"Required input parameter '" + declaredParam.getName() + "' is missing");
212 						}
213 						StatementCreatorUtils.setParameterValue(csToUse, sqlColIndx, declaredParam, inValue);
214 					}
215 					sqlColIndx++;
216 				}
217 			}
218 
219 			return cs;
220 		}
221 
222 		@Override
223 		public String getSql() {
224 			return callString;
225 		}
226 
227 		@Override
228 		public void cleanupParameters() {
229 			if (this.inParameters != null) {
230 				StatementCreatorUtils.cleanupParameters(this.inParameters.values());
231 			}
232 		}
233 
234 		@Override
235 		public String toString() {
236 			StringBuilder sb = new StringBuilder();
237 			sb.append("CallableStatementCreatorFactory.CallableStatementCreatorImpl: sql=[");
238 			sb.append(callString).append("]; parameters=").append(this.inParameters);
239 			return sb.toString();
240 		}
241 	}
242 
243 }