1 /*
2 * Copyright 2004 Clinton Begin
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 package com.ibatis.sqlmap.engine.execution;
17
18 import com.ibatis.sqlmap.engine.mapping.parameter.BasicParameterMapping;
19 import com.ibatis.sqlmap.engine.mapping.parameter.ParameterMap;
20 import com.ibatis.sqlmap.engine.mapping.parameter.ParameterMapping;
21 import com.ibatis.sqlmap.engine.mapping.result.ResultMap;
22 import com.ibatis.sqlmap.engine.mapping.statement.RowHandlerCallback;
23 import com.ibatis.sqlmap.engine.scope.ErrorContext;
24 import com.ibatis.sqlmap.engine.scope.RequestScope;
25 import com.ibatis.sqlmap.engine.scope.SessionScope;
26
27 import java.sql;
28 import java.util.ArrayList;
29 import java.util.List;
30
31 /**
32 * Class responsible for executing the SQL
33 */
34 public class SqlExecutor {
35
36 //
37 // Constants
38 //
39
40 /**
41 * Constant to let us know not to skip anything
42 */
43 public static final int NO_SKIPPED_RESULTS = 0;
44
45 /**
46 * Constant to let us know to include all records
47 */
48 public static final int NO_MAXIMUM_RESULTS = -999999;
49
50 //
51 // Public Methods
52 //
53
54 /**
55 * Execute an update
56 *
57 * @param request - the request scope
58 * @param conn - the database connection
59 * @param sql - the sql statement to execute
60 * @param parameters - the parameters for the sql statement
61 *
62 * @return - the number of records changed
63 *
64 * @throws SQLException - if the update fails
65 */
66 public int executeUpdate(RequestScope request, Connection conn, String sql, Object[] parameters)
67 throws SQLException {
68 ErrorContext errorContext = request.getErrorContext();
69 errorContext.setActivity("executing update");
70 errorContext.setObjectId(sql);
71
72 PreparedStatement ps = null;
73 int rows = 0;
74
75 try {
76 errorContext.setMoreInfo("Check the SQL Statement (preparation failed).");
77 ps = conn.prepareStatement(sql);
78
79 errorContext.setMoreInfo("Check the parameters (set parameters failed).");
80 request.getParameterMap().setParameters(request, ps, parameters);
81
82 errorContext.setMoreInfo("Check the statement (update failed).");
83
84 ps.execute();
85 rows = ps.getUpdateCount();
86 }
87 finally {
88 closeStatement(ps);
89 }
90
91 return rows;
92 }
93
94 /**
95 * Adds a statement to a batch
96 *
97 * @param request - the request scope
98 * @param conn - the database connection
99 * @param sql - the sql statement
100 * @param parameters - the parameters for the statement
101 *
102 * @throws SQLException - if the statement fails
103 */
104 public void addBatch(RequestScope request, Connection conn, String sql, Object[] parameters)
105 throws SQLException {
106 Batch batch = (Batch) request.getSession().getBatch();
107 if (batch == null) {
108 batch = new Batch();
109 request.getSession().setBatch(batch);
110 }
111 batch.addBatch(request, conn, sql, parameters);
112 }
113
114 /**
115 * Execute a batch of statements
116 *
117 * @param session - the session scope
118 *
119 * @return - the number of rows impacted by the batch
120 *
121 * @throws SQLException - if a statement fails
122 */
123 public int executeBatch(SessionScope session)
124 throws SQLException {
125 int rows = 0;
126 Batch batch = (Batch) session.getBatch();
127 if (batch != null) {
128 try {
129 rows = batch.executeBatch();
130 } finally {
131 batch.cleanupBatch();
132 }
133 }
134 return rows;
135 }
136
137 /**
138 * Long form of the method to execute a query
139 *
140 * @param request - the request scope
141 * @param conn - the database connection
142 * @param sql - the SQL statement to execute
143 * @param parameters - the parameters for the statement
144 * @param skipResults - the number of results to skip
145 * @param maxResults - the maximum number of results to return
146 * @param callback - the row handler for the query
147 *
148 * @throws SQLException - if the query fails
149 */
150 public void executeQuery(RequestScope request, Connection conn, String sql, Object[] parameters,
151 int skipResults, int maxResults, RowHandlerCallback callback)
152 throws SQLException {
153 ErrorContext errorContext = request.getErrorContext();
154 errorContext.setActivity("executing query");
155 errorContext.setObjectId(sql);
156
157 PreparedStatement ps = null;
158 ResultSet rs = null;
159
160 try {
161 errorContext.setMoreInfo("Check the SQL Statement (preparation failed).");
162
163 Integer rsType = request.getStatement().getResultSetType();
164 if (rsType != null) {
165 ps = conn.prepareStatement(sql, rsType.intValue(), ResultSet.CONCUR_READ_ONLY);
166 } else {
167 ps = conn.prepareStatement(sql);
168 }
169
170 Integer fetchSize = request.getStatement().getFetchSize();
171 if (fetchSize != null) {
172 ps.setFetchSize(fetchSize.intValue());
173 }
174
175 errorContext.setMoreInfo("Check the parameters (set parameters failed).");
176 request.getParameterMap().setParameters(request, ps, parameters);
177
178 errorContext.setMoreInfo("Check the statement (query failed).");
179
180 ps.execute();
181 rs = ps.getResultSet();
182
183 errorContext.setMoreInfo("Check the results (failed to retrieve results).");
184 handleResults(request, rs, skipResults, maxResults, callback);
185
186 // clear out remaining results
187 while (ps.getMoreResults());
188
189 } finally {
190 try {
191 closeResultSet(rs);
192 } finally {
193 closeStatement(ps);
194 }
195 }
196
197 }
198
199 /**
200 * Execute a stored procedure that updates data
201 *
202 * @param request - the request scope
203 * @param conn - the database connection
204 * @param sql - the SQL to call the procedure
205 * @param parameters - the parameters for the procedure
206 *
207 * @return - the rows impacted by the procedure
208 *
209 * @throws SQLException - if the procedure fails
210 */
211 public int executeUpdateProcedure(RequestScope request, Connection conn, String sql, Object[] parameters)
212 throws SQLException {
213 ErrorContext errorContext = request.getErrorContext();
214 errorContext.setActivity("executing update procedure");
215 errorContext.setObjectId(sql);
216
217 CallableStatement cs = null;
218 int rows = 0;
219
220 try {
221 errorContext.setMoreInfo("Check the SQL Statement (preparation failed).");
222 cs = conn.prepareCall(sql);
223
224 ParameterMap parameterMap = request.getParameterMap();
225
226 ParameterMapping[] mappings = parameterMap.getParameterMappings();
227
228 errorContext.setMoreInfo("Check the output parameters (register output parameters failed).");
229 registerOutputParameters(cs, mappings);
230
231 errorContext.setMoreInfo("Check the parameters (set parameters failed).");
232 parameterMap.setParameters(request, cs, parameters);
233
234 errorContext.setMoreInfo("Check the statement (update procedure failed).");
235
236 cs.execute();
237 rows = cs.getUpdateCount();
238
239 errorContext.setMoreInfo("Check the output parameters (retrieval of output parameters failed).");
240 retrieveOutputParameters(cs, mappings, parameters);
241 } finally {
242 closeStatement(cs);
243 }
244
245 return rows;
246 }
247
248 /**
249 * Execute a stored procedure
250 *
251 * @param request - the request scope
252 * @param conn - the database connection
253 * @param sql - the sql to call the procedure
254 * @param parameters - the parameters for the procedure
255 * @param skipResults - the number of results to skip
256 * @param maxResults - the maximum number of results to return
257 * @param callback - a row handler for processing the results
258 *
259 * @throws SQLException - if the procedure fails
260 */
261 public void executeQueryProcedure(RequestScope request, Connection conn, String sql, Object[] parameters,
262 int skipResults, int maxResults, RowHandlerCallback callback)
263 throws SQLException {
264 ErrorContext errorContext = request.getErrorContext();
265 errorContext.setActivity("executing query procedure");
266 errorContext.setObjectId(sql);
267
268 CallableStatement cs = null;
269 ResultSet rs = null;
270
271 try {
272 errorContext.setMoreInfo("Check the SQL Statement (preparation failed).");
273 cs = conn.prepareCall(sql);
274
275 ParameterMap parameterMap = request.getParameterMap();
276
277 ParameterMapping[] mappings = parameterMap.getParameterMappings();
278
279 errorContext.setMoreInfo("Check the output parameters (register output parameters failed).");
280 registerOutputParameters(cs, mappings);
281
282 errorContext.setMoreInfo("Check the parameters (set parameters failed).");
283 parameterMap.setParameters(request, cs, parameters);
284
285 errorContext.setMoreInfo("Check the statement (update procedure failed).");
286
287 cs.execute();
288 rs = cs.getResultSet();
289
290 errorContext.setMoreInfo("Check the results (failed to retrieve results).");
291 handleResults(request, rs, skipResults, maxResults, callback);
292
293 errorContext.setMoreInfo("Check the output parameters (retrieval of output parameters failed).");
294 retrieveOutputParameters(cs, mappings, parameters);
295
296 } finally {
297 try {
298 closeResultSet(rs);
299 } finally {
300 closeStatement(cs);
301 }
302 }
303
304 }
305
306 /**
307 * Clean up any batches on the session
308 *
309 * @param session - the session to clean up
310 */
311 public void cleanup(SessionScope session) {
312 Batch batch = (Batch) session.getBatch();
313 if (batch != null) {
314 batch.cleanupBatch();
315 session.setBatch(null);
316 }
317 }
318
319 //
320 // Private Methods
321 //
322
323 private void retrieveOutputParameters(CallableStatement cs, ParameterMapping[] mappings, Object[] parameters) throws SQLException {
324 for (int i = 0; i < mappings.length; i++) {
325 BasicParameterMapping mapping = ((BasicParameterMapping) mappings[i]);
326 if (mapping.isOutputAllowed()) {
327 Object o = mapping.getTypeHandler().getResult(cs, i + 1);
328 parameters[i] = o;
329 }
330 }
331 }
332
333 private void registerOutputParameters(CallableStatement cs, ParameterMapping[] mappings) throws SQLException {
334 for (int i = 0; i < mappings.length; i++) {
335 BasicParameterMapping mapping = ((BasicParameterMapping) mappings[i]);
336 if (mapping.isOutputAllowed()) {
337 cs.registerOutParameter(i + 1, mapping.getJdbcType());
338 }
339 }
340 }
341
342 private void handleResults(RequestScope request, ResultSet rs, int skipResults, int maxResults, RowHandlerCallback callback) throws SQLException {
343 try {
344 request.setResultSet(rs);
345 ResultMap resultMap = request.getResultMap();
346 if (resultMap != null) {
347 // Skip Results
348 if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
349 if (skipResults > 0) {
350 rs.absolute(skipResults);
351 }
352 } else {
353 for (int i = 0; i < skipResults; i++) {
354 if (!rs.next()) {
355 break;
356 }
357 }
358 }
359
360 // Get Results
361 int resultsFetched = 0;
362 while ((maxResults == SqlExecutor.NO_MAXIMUM_RESULTS || resultsFetched < maxResults) && rs.next()) {
363 Object[] columnValues = resultMap.resolveSubMap(request, rs).getResults(request, rs);
364 callback.handleResultObject(request, columnValues, rs);
365 resultsFetched++;
366 }
367 }
368 } finally {
369 request.setResultSet(null);
370 }
371 }
372
373 /**
374 * @param ps
375 */
376 private static void closeStatement(PreparedStatement ps) {
377 if (ps != null) {
378 try {
379 ps.close();
380 } catch (SQLException e) {
381 // ignore
382 }
383 }
384 }
385
386 /**
387 * @param rs
388 */
389 private static void closeResultSet(ResultSet rs) {
390 if (rs != null) {
391 try {
392 rs.close();
393 } catch (SQLException e) {
394 // ignore
395 }
396 }
397 }
398
399 //
400 // Inner Classes
401 //
402
403 private static class Batch {
404 private String currentSql;
405 private List statementList = new ArrayList();
406 private int size;
407 private static final int SUCCESS_NO_INFO = -2;
408 private static final int EXECUTE_FAILED = -3;
409
410 /**
411 * Create a new batch
412 */
413 public Batch() {
414 this.size = 0;
415 }
416
417 /**
418 * Getter for the batch size
419 *
420 * @return - the batch size
421 */
422 public int getSize() {
423 return size;
424 }
425
426 /**
427 * Add a prepared statement to the batch
428 *
429 * @param request - the request scope
430 * @param conn - the database connection
431 * @param sql - the SQL to add
432 * @param parameters - the parameters for the SQL
433 *
434 * @throws SQLException - if the prepare for the SQL fails
435 */
436 public void addBatch(RequestScope request, Connection conn, String sql, Object[] parameters) throws SQLException {
437 PreparedStatement ps = null;
438 if (currentSql != null
439 && sql.hashCode() == currentSql.hashCode()
440 && sql.length() == currentSql.length()) {
441 int last = statementList.size() - 1;
442 ps = (PreparedStatement) statementList.get(last);
443 } else {
444 ps = conn.prepareStatement(sql);
445 currentSql = sql;
446 statementList.add(ps);
447 }
448 request.getParameterMap().setParameters(request, ps, parameters);
449 ps.addBatch();
450 size++;
451 }
452
453 /**
454 * Execute the current session's batch
455 *
456 * @return - the number of rows updated
457 *
458 * @throws SQLException - if the batch fails
459 */
460 public int executeBatch() throws SQLException {
461 int totalRowCount = 0;
462 for (int i = 0, n = statementList.size(); i < n; i++) {
463 PreparedStatement ps = (PreparedStatement) statementList.get(i);
464 int[] rowCounts = ps.executeBatch();
465 for (int j = 0; j < rowCounts.length; j++) {
466 if (rowCounts[j] == SUCCESS_NO_INFO) {
467 // do nothing
468 } else if (rowCounts[j] == EXECUTE_FAILED) {
469 throw new SQLException("The batched statement at index " + j + " failed to execute.");
470 } else {
471 totalRowCount += rowCounts[j];
472 }
473 }
474 }
475 return totalRowCount;
476 }
477
478 /**
479 * Close all the statements in the batch and clear all the statements
480 */
481 public void cleanupBatch() {
482 for (int i = 0, n = statementList.size(); i < n; i++) {
483 PreparedStatement ps = (PreparedStatement) statementList.get(i);
484 closeStatement(ps);
485 }
486 currentSql = null;
487 statementList.clear();
488 size = 0;
489 }
490 }
491
492 }