View Javadoc
1   /*
2    * Copyright 2004-2022 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    *    https://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.config.SqlMapConfiguration;
19  import com.ibatis.sqlmap.engine.impl.SqlMapClientImpl;
20  import com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate;
21  import com.ibatis.sqlmap.engine.mapping.parameter.ParameterMap;
22  import com.ibatis.sqlmap.engine.mapping.parameter.ParameterMapping;
23  import com.ibatis.sqlmap.engine.mapping.result.ResultMap;
24  import com.ibatis.sqlmap.engine.mapping.result.ResultObjectFactoryUtil;
25  import com.ibatis.sqlmap.engine.mapping.statement.DefaultRowHandler;
26  import com.ibatis.sqlmap.engine.mapping.statement.MappedStatement;
27  import com.ibatis.sqlmap.engine.mapping.statement.RowHandlerCallback;
28  import com.ibatis.sqlmap.engine.scope.ErrorContext;
29  import com.ibatis.sqlmap.engine.scope.SessionScope;
30  import com.ibatis.sqlmap.engine.scope.StatementScope;
31  
32  import java.sql.BatchUpdateException;
33  import java.sql.CallableStatement;
34  import java.sql.Connection;
35  import java.sql.PreparedStatement;
36  import java.sql.ResultSet;
37  import java.sql.SQLException;
38  import java.sql.Statement;
39  import java.sql.Types;
40  import java.util.ArrayList;
41  import java.util.List;
42  import java.util.Properties;
43  
44  /**
45   * Class responsible for executing the SQL.
46   */
47  public class DefaultSqlExecutor implements SqlExecutor {
48  
49    //
50    // Public Methods
51    //
52  
53    /**
54     * Execute an update
55     *
56     * @param statementScope
57     *          - the request scope
58     * @param conn
59     *          - the database connection
60     * @param sql
61     *          - the sql statement to execute
62     * @param parameters
63     *          - the parameters for the sql statement
64     *
65     * @return - the number of records changed
66     *
67     * @throws SQLException
68     *           - if the update fails
69     */
70    public int executeUpdate(StatementScope statementScope, Connection conn, String sql, Object[] parameters)
71        throws SQLException {
72      ErrorContext errorContext = statementScope.getErrorContext();
73      errorContext.setActivity("executing update");
74      errorContext.setObjectId(sql);
75      PreparedStatement ps = null;
76      setupResultObjectFactory(statementScope);
77      int rows = 0;
78      try {
79        errorContext.setMoreInfo("Check the SQL Statement (preparation failed).");
80        ps = prepareStatement(statementScope.getSession(), conn, sql);
81        setStatementTimeout(statementScope.getStatement(), ps);
82        errorContext.setMoreInfo("Check the parameters (set parameters failed).");
83        statementScope.getParameterMap().setParameters(statementScope, ps, parameters);
84        errorContext.setMoreInfo("Check the statement (update failed).");
85        ps.execute();
86        rows = ps.getUpdateCount();
87      } finally {
88        closeStatement(statementScope.getSession(), ps);
89        cleanupResultObjectFactory();
90      }
91      return rows;
92    }
93  
94    /**
95     * Adds a statement to a batch
96     *
97     * @param statementScope
98     *          - the request scope
99     * @param conn
100    *          - the database connection
101    * @param sql
102    *          - the sql statement
103    * @param parameters
104    *          - the parameters for the statement
105    *
106    * @throws SQLException
107    *           - if the statement fails
108    */
109   public void addBatch(StatementScope statementScope, Connection conn, String sql, Object[] parameters)
110       throws SQLException {
111     Batch batch = (Batch) statementScope.getSession().getBatch();
112     if (batch == null) {
113       batch = new Batch();
114       statementScope.getSession().setBatch(batch);
115     }
116     batch.addBatch(statementScope, conn, sql, parameters);
117   }
118 
119   /**
120    * Execute a batch of statements
121    *
122    * @param sessionScope
123    *          - the session scope
124    *
125    * @return - the number of rows impacted by the batch
126    *
127    * @throws SQLException
128    *           - if a statement fails
129    */
130   public int executeBatch(SessionScope sessionScope) throws SQLException {
131     int rows = 0;
132     Batch batch = (Batch) sessionScope.getBatch();
133     if (batch != null) {
134       try {
135         rows = batch.executeBatch();
136       } finally {
137         batch.cleanupBatch(sessionScope);
138       }
139     }
140     return rows;
141   }
142 
143   /**
144    * Execute a batch of statements
145    *
146    * @param sessionScope
147    *          - the session scope
148    *
149    * @return - a List of BatchResult objects (may be null if no batch has been initiated). There will be one BatchResult
150    *         object in the list for each sub-batch executed
151    *
152    * @throws SQLException
153    *           if a database access error occurs, or the drive does not support batch statements
154    * @throws BatchException
155    *           if the driver throws BatchUpdateException
156    */
157   public List executeBatchDetailed(SessionScope sessionScope) throws SQLException, BatchException {
158     List answer = null;
159     Batch batch = (Batch) sessionScope.getBatch();
160     if (batch != null) {
161       try {
162         answer = batch.executeBatchDetailed();
163       } finally {
164         batch.cleanupBatch(sessionScope);
165       }
166     }
167     return answer;
168   }
169 
170   /**
171    * Long form of the method to execute a query
172    *
173    * @param statementScope
174    *          - the request scope
175    * @param conn
176    *          - the database connection
177    * @param sql
178    *          - the SQL statement to execute
179    * @param parameters
180    *          - the parameters for the statement
181    * @param skipResults
182    *          - the number of results to skip
183    * @param maxResults
184    *          - the maximum number of results to return
185    * @param callback
186    *          - the row handler for the query
187    *
188    * @throws SQLException
189    *           - if the query fails
190    */
191   public void executeQuery(StatementScope statementScope, Connection conn, String sql, Object[] parameters,
192       int skipResults, int maxResults, RowHandlerCallback callback) throws SQLException {
193     ErrorContext errorContext = statementScope.getErrorContext();
194     errorContext.setActivity("executing query");
195     errorContext.setObjectId(sql);
196     PreparedStatement ps = null;
197     ResultSet rs = null;
198     setupResultObjectFactory(statementScope);
199     try {
200       errorContext.setMoreInfo("Check the SQL Statement (preparation failed).");
201       Integer rsType = statementScope.getStatement().getResultSetType();
202       if (rsType != null) {
203         ps = prepareStatement(statementScope.getSession(), conn, sql, rsType);
204       } else {
205         ps = prepareStatement(statementScope.getSession(), conn, sql);
206       }
207       setStatementTimeout(statementScope.getStatement(), ps);
208       Integer fetchSize = statementScope.getStatement().getFetchSize();
209       if (fetchSize != null) {
210         ps.setFetchSize(fetchSize.intValue());
211       }
212       errorContext.setMoreInfo("Check the parameters (set parameters failed).");
213       statementScope.getParameterMap().setParameters(statementScope, ps, parameters);
214       errorContext.setMoreInfo("Check the statement (query failed).");
215       ps.execute();
216       errorContext.setMoreInfo("Check the results (failed to retrieve results).");
217 
218       // Begin ResultSet Handling
219       rs = handleMultipleResults(ps, statementScope, skipResults, maxResults, callback);
220       // End ResultSet Handling
221     } finally {
222       try {
223         closeResultSet(rs);
224       } finally {
225         closeStatement(statementScope.getSession(), ps);
226         cleanupResultObjectFactory();
227       }
228     }
229 
230   }
231 
232   /**
233    * Execute a stored procedure that updates data
234    *
235    * @param statementScope
236    *          - the request scope
237    * @param conn
238    *          - the database connection
239    * @param sql
240    *          - the SQL to call the procedure
241    * @param parameters
242    *          - the parameters for the procedure
243    *
244    * @return - the rows impacted by the procedure
245    *
246    * @throws SQLException
247    *           - if the procedure fails
248    */
249   public int executeUpdateProcedure(StatementScope statementScope, Connection conn, String sql, Object[] parameters)
250       throws SQLException {
251     ErrorContext errorContext = statementScope.getErrorContext();
252     errorContext.setActivity("executing update procedure");
253     errorContext.setObjectId(sql);
254     CallableStatement cs = null;
255     setupResultObjectFactory(statementScope);
256     int rows = 0;
257     try {
258       errorContext.setMoreInfo("Check the SQL Statement (preparation failed).");
259       cs = prepareCall(statementScope.getSession(), conn, sql);
260       setStatementTimeout(statementScope.getStatement(), cs);
261       ParameterMap parameterMap = statementScope.getParameterMap();
262       ParameterMapping[] mappings = parameterMap.getParameterMappings();
263       errorContext.setMoreInfo("Check the output parameters (register output parameters failed).");
264       registerOutputParameters(cs, mappings);
265       errorContext.setMoreInfo("Check the parameters (set parameters failed).");
266       parameterMap.setParameters(statementScope, cs, parameters);
267       errorContext.setMoreInfo("Check the statement (update procedure failed).");
268       cs.execute();
269       rows = cs.getUpdateCount();
270       errorContext.setMoreInfo("Check the output parameters (retrieval of output parameters failed).");
271       retrieveOutputParameters(statementScope, cs, mappings, parameters, null);
272     } finally {
273       closeStatement(statementScope.getSession(), cs);
274       cleanupResultObjectFactory();
275     }
276     return rows;
277   }
278 
279   /**
280    * Execute a stored procedure
281    *
282    * @param statementScope
283    *          - the request scope
284    * @param conn
285    *          - the database connection
286    * @param sql
287    *          - the sql to call the procedure
288    * @param parameters
289    *          - the parameters for the procedure
290    * @param skipResults
291    *          - the number of results to skip
292    * @param maxResults
293    *          - the maximum number of results to return
294    * @param callback
295    *          - a row handler for processing the results
296    *
297    * @throws SQLException
298    *           - if the procedure fails
299    */
300   public void executeQueryProcedure(StatementScope statementScope, Connection conn, String sql, Object[] parameters,
301       int skipResults, int maxResults, RowHandlerCallback callback) throws SQLException {
302     ErrorContext errorContext = statementScope.getErrorContext();
303     errorContext.setActivity("executing query procedure");
304     errorContext.setObjectId(sql);
305     CallableStatement cs = null;
306     ResultSet rs = null;
307     setupResultObjectFactory(statementScope);
308     try {
309       errorContext.setMoreInfo("Check the SQL Statement (preparation failed).");
310       Integer rsType = statementScope.getStatement().getResultSetType();
311       if (rsType != null) {
312         cs = prepareCall(statementScope.getSession(), conn, sql, rsType);
313       } else {
314         cs = prepareCall(statementScope.getSession(), conn, sql);
315       }
316       setStatementTimeout(statementScope.getStatement(), cs);
317       Integer fetchSize = statementScope.getStatement().getFetchSize();
318       if (fetchSize != null) {
319         cs.setFetchSize(fetchSize.intValue());
320       }
321       ParameterMap parameterMap = statementScope.getParameterMap();
322       ParameterMapping[] mappings = parameterMap.getParameterMappings();
323       errorContext.setMoreInfo("Check the output parameters (register output parameters failed).");
324       registerOutputParameters(cs, mappings);
325       errorContext.setMoreInfo("Check the parameters (set parameters failed).");
326       parameterMap.setParameters(statementScope, cs, parameters);
327       errorContext.setMoreInfo("Check the statement (update procedure failed).");
328       cs.execute();
329       errorContext.setMoreInfo("Check the results (failed to retrieve results).");
330 
331       // Begin ResultSet Handling
332       rs = handleMultipleResults(cs, statementScope, skipResults, maxResults, callback);
333       // End ResultSet Handling
334       errorContext.setMoreInfo("Check the output parameters (retrieval of output parameters failed).");
335       retrieveOutputParameters(statementScope, cs, mappings, parameters, callback);
336 
337     } finally {
338       try {
339         closeResultSet(rs);
340       } finally {
341         closeStatement(statementScope.getSession(), cs);
342         cleanupResultObjectFactory();
343       }
344     }
345   }
346 
347   public void init(SqlMapConfiguration config, Properties globalProps) {
348     // No implementation is required in DefaultSqlExecutor.
349   }
350 
351   /**
352    * Handle multiple results.
353    *
354    * @param ps
355    *          the ps
356    * @param statementScope
357    *          the statement scope
358    * @param skipResults
359    *          the skip results
360    * @param maxResults
361    *          the max results
362    * @param callback
363    *          the callback
364    *
365    * @return the result set
366    *
367    * @throws SQLException
368    *           the SQL exception
369    */
370   private ResultSet handleMultipleResults(PreparedStatement ps, StatementScope statementScope, int skipResults,
371       int maxResults, RowHandlerCallback callback) throws SQLException {
372     ResultSet rs;
373     rs = getFirstResultSet(statementScope, ps);
374     if (rs != null) {
375       handleResults(statementScope, rs, skipResults, maxResults, callback);
376     }
377 
378     // Multiple ResultSet handling
379     if (callback.getRowHandler() instanceof DefaultRowHandler) {
380       MappedStatement statement = statementScope.getStatement();
381       DefaultRowHandler defaultRowHandler = ((DefaultRowHandler) callback.getRowHandler());
382       if (statement.hasMultipleResultMaps()) {
383         List multipleResults = new ArrayList();
384         multipleResults.add(defaultRowHandler.getList());
385         ResultMap[] resultMaps = statement.getAdditionalResultMaps();
386         int i = 0;
387         while (moveToNextResultsSafely(statementScope, ps)) {
388           if (i >= resultMaps.length)
389             break;
390           ResultMap rm = resultMaps[i];
391           statementScope.setResultMap(rm);
392           rs = ps.getResultSet();
393           DefaultRowHandler rh = new DefaultRowHandler();
394           handleResults(statementScope, rs, skipResults, maxResults, new RowHandlerCallback(rm, null, rh));
395           multipleResults.add(rh.getList());
396           i++;
397         }
398         defaultRowHandler.setList(multipleResults);
399         statementScope.setResultMap(statement.getResultMap());
400       } else {
401         while (moveToNextResultsSafely(statementScope, ps))
402           ;
403       }
404     }
405     // End additional ResultSet handling
406     return rs;
407   }
408 
409   /**
410    * Gets the first result set.
411    *
412    * @param scope
413    *          the scope
414    * @param stmt
415    *          the stmt
416    *
417    * @return the first result set
418    *
419    * @throws SQLException
420    *           the SQL exception
421    */
422   private ResultSet getFirstResultSet(StatementScope scope, Statement stmt) throws SQLException {
423     ResultSet rs = null;
424     boolean hasMoreResults = true;
425     while (hasMoreResults) {
426       rs = stmt.getResultSet();
427       if (rs != null) {
428         break;
429       }
430       hasMoreResults = moveToNextResultsIfPresent(scope, stmt);
431     }
432     return rs;
433   }
434 
435   /**
436    * Move to next results if present.
437    *
438    * @param scope
439    *          the scope
440    * @param stmt
441    *          the stmt
442    *
443    * @return true, if successful
444    *
445    * @throws SQLException
446    *           the SQL exception
447    */
448   private boolean moveToNextResultsIfPresent(StatementScope scope, Statement stmt) throws SQLException {
449     boolean moreResults;
450     // This is the messed up JDBC approach for determining if there are more results
451     boolean movedToNextResultsSafely = moveToNextResultsSafely(scope, stmt);
452     int updateCount = stmt.getUpdateCount();
453 
454     moreResults = !(!movedToNextResultsSafely && (updateCount == -1));
455 
456     // ibatis-384: workaround for mysql not returning -1 for stmt.getUpdateCount()
457     if (moreResults == true) {
458       moreResults = !(!movedToNextResultsSafely && !isMultipleResultSetSupportPresent(scope, stmt));
459     }
460 
461     return moreResults;
462   }
463 
464   /**
465    * Move to next results safely.
466    *
467    * @param scope
468    *          the scope
469    * @param stmt
470    *          the stmt
471    *
472    * @return true, if successful
473    *
474    * @throws SQLException
475    *           the SQL exception
476    */
477   private boolean moveToNextResultsSafely(StatementScope scope, Statement stmt) throws SQLException {
478     if (isMultipleResultSetSupportPresent(scope, stmt)) {
479       return stmt.getMoreResults();
480     }
481     return false;
482   }
483 
484   /**
485    * checks whether multiple result set support is present - either by direct support of the database driver or by
486    * forcing it.
487    *
488    * @param scope
489    *          the scope
490    * @param stmt
491    *          the stmt
492    *
493    * @return true, if is multiple result set support present
494    *
495    * @throws SQLException
496    *           the SQL exception
497    */
498   private boolean isMultipleResultSetSupportPresent(StatementScope scope, Statement stmt) throws SQLException {
499     return forceMultipleResultSetSupport(scope) || stmt.getConnection().getMetaData().supportsMultipleResultSets();
500   }
501 
502   /**
503    * Force multiple result set support.
504    *
505    * @param scope
506    *          the scope
507    *
508    * @return true, if successful
509    */
510   private boolean forceMultipleResultSetSupport(StatementScope scope) {
511     return ((SqlMapClientImpl) scope.getSession().getSqlMapClient()).getDelegate().isForceMultipleResultSetSupport();
512   }
513 
514   /**
515    * Handle results.
516    *
517    * @param statementScope
518    *          the statement scope
519    * @param rs
520    *          the rs
521    * @param skipResults
522    *          the skip results
523    * @param maxResults
524    *          the max results
525    * @param callback
526    *          the callback
527    *
528    * @throws SQLException
529    *           the SQL exception
530    */
531   private void handleResults(StatementScope statementScope, ResultSet rs, int skipResults, int maxResults,
532       RowHandlerCallback callback) throws SQLException {
533     try {
534       statementScope.setResultSet(rs);
535       ResultMap resultMap = statementScope.getResultMap();
536       if (resultMap != null) {
537         // Skip Results
538         if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
539           if (skipResults > 0) {
540             rs.absolute(skipResults);
541           }
542         } else {
543           for (int i = 0; i < skipResults; i++) {
544             if (!rs.next()) {
545               return;
546             }
547           }
548         }
549 
550         // Get Results
551         int resultsFetched = 0;
552         while ((maxResults == NO_MAXIMUM_RESULTS || resultsFetched < maxResults) && rs.next()) {
553           Object[] columnValues = resultMap.resolveSubMap(statementScope, rs).getResults(statementScope, rs);
554           callback.handleResultObject(statementScope, columnValues, rs);
555           resultsFetched++;
556         }
557       }
558     } finally {
559       statementScope.setResultSet(null);
560     }
561   }
562 
563   /**
564    * Retrieve output parameters.
565    *
566    * @param statementScope
567    *          the statement scope
568    * @param cs
569    *          the cs
570    * @param mappings
571    *          the mappings
572    * @param parameters
573    *          the parameters
574    * @param callback
575    *          the callback
576    *
577    * @throws SQLException
578    *           the SQL exception
579    */
580   private void retrieveOutputParameters(StatementScope statementScope, CallableStatement cs,
581       ParameterMapping[] mappings, Object[] parameters, RowHandlerCallback callback) throws SQLException {
582     for (int i = 0; i < mappings.length; i++) {
583       ParameterMapping mapping = ((ParameterMapping) mappings[i]);
584       if (mapping.isOutputAllowed()) {
585         if ("java.sql.ResultSet".equalsIgnoreCase(mapping.getJavaTypeName())) {
586           ResultSet rs = (ResultSet) cs.getObject(i + 1);
587           ResultMap resultMap;
588           if (mapping.getResultMapName() == null) {
589             resultMap = statementScope.getResultMap();
590             handleOutputParameterResults(statementScope, resultMap, rs, callback);
591           } else {
592             SqlMapClientImpl client = (SqlMapClientImpl) statementScope.getSession().getSqlMapClient();
593             resultMap = client.getDelegate().getResultMap(mapping.getResultMapName());
594             DefaultRowHandler rowHandler = new DefaultRowHandler();
595             RowHandlerCallback handlerCallback = new RowHandlerCallback(resultMap, null, rowHandler);
596             handleOutputParameterResults(statementScope, resultMap, rs, handlerCallback);
597             parameters[i] = rowHandler.getList();
598           }
599           rs.close();
600         } else {
601           parameters[i] = mapping.getTypeHandler().getResult(cs, i + 1);
602         }
603       }
604     }
605   }
606 
607   /**
608    * Register output parameters.
609    *
610    * @param cs
611    *          the cs
612    * @param mappings
613    *          the mappings
614    *
615    * @throws SQLException
616    *           the SQL exception
617    */
618   private void registerOutputParameters(CallableStatement cs, ParameterMapping[] mappings) throws SQLException {
619     for (int i = 0; i < mappings.length; i++) {
620       ParameterMapping mapping = ((ParameterMapping) mappings[i]);
621       if (mapping.isOutputAllowed()) {
622         if (null != mapping.getTypeName() && !mapping.getTypeName().equals("")) { // @added
623           cs.registerOutParameter(i + 1, mapping.getJdbcType(), mapping.getTypeName());
624         } else {
625           if (mapping.getNumericScale() != null
626               && (mapping.getJdbcType() == Types.NUMERIC || mapping.getJdbcType() == Types.DECIMAL)) {
627             cs.registerOutParameter(i + 1, mapping.getJdbcType(), mapping.getNumericScale().intValue());
628           } else {
629             cs.registerOutParameter(i + 1, mapping.getJdbcType());
630           }
631         }
632       }
633     }
634   }
635 
636   /**
637    * Handle output parameter results.
638    *
639    * @param statementScope
640    *          the statement scope
641    * @param resultMap
642    *          the result map
643    * @param rs
644    *          the rs
645    * @param callback
646    *          the callback
647    *
648    * @throws SQLException
649    *           the SQL exception
650    */
651   private void handleOutputParameterResults(StatementScope statementScope, ResultMap resultMap, ResultSet rs,
652       RowHandlerCallback callback) throws SQLException {
653     ResultMap orig = statementScope.getResultMap();
654     try {
655       statementScope.setResultSet(rs);
656       if (resultMap != null) {
657         statementScope.setResultMap(resultMap);
658 
659         // Get Results
660         while (rs.next()) {
661           Object[] columnValues = resultMap.resolveSubMap(statementScope, rs).getResults(statementScope, rs);
662           callback.handleResultObject(statementScope, columnValues, rs);
663         }
664       }
665     } finally {
666       statementScope.setResultSet(null);
667       statementScope.setResultMap(orig);
668     }
669   }
670 
671   /**
672    * Clean up any batches on the session
673    *
674    * @param sessionScope
675    *          - the session to clean up
676    */
677   public void cleanup(SessionScope sessionScope) {
678     Batch batch = (Batch) sessionScope.getBatch();
679     if (batch != null) {
680       batch.cleanupBatch(sessionScope);
681       sessionScope.setBatch(null);
682     }
683   }
684 
685   /**
686    * Prepare statement.
687    *
688    * @param sessionScope
689    *          the session scope
690    * @param conn
691    *          the conn
692    * @param sql
693    *          the sql
694    * @param rsType
695    *          the rs type
696    *
697    * @return the prepared statement
698    *
699    * @throws SQLException
700    *           the SQL exception
701    */
702   private PreparedStatement prepareStatement(SessionScope sessionScope, Connection conn, String sql, Integer rsType)
703       throws SQLException {
704     SqlMapExecutorDelegate delegate = ((SqlMapClientImpl) sessionScope.getSqlMapExecutor()).getDelegate();
705     if (sessionScope.hasPreparedStatementFor(sql)) {
706       return sessionScope.getPreparedStatement((sql));
707     } else {
708       PreparedStatement ps = conn.prepareStatement(sql, rsType.intValue(), ResultSet.CONCUR_READ_ONLY);
709       sessionScope.putPreparedStatement(delegate, sql, ps);
710       return ps;
711     }
712   }
713 
714   /**
715    * Prepare call.
716    *
717    * @param sessionScope
718    *          the session scope
719    * @param conn
720    *          the conn
721    * @param sql
722    *          the sql
723    * @param rsType
724    *          the rs type
725    *
726    * @return the callable statement
727    *
728    * @throws SQLException
729    *           the SQL exception
730    */
731   private CallableStatement prepareCall(SessionScope sessionScope, Connection conn, String sql, Integer rsType)
732       throws SQLException {
733     SqlMapExecutorDelegate delegate = ((SqlMapClientImpl) sessionScope.getSqlMapExecutor()).getDelegate();
734     if (sessionScope.hasPreparedStatementFor(sql)) {
735       return (CallableStatement) sessionScope.getPreparedStatement((sql));
736     } else {
737       CallableStatement cs = conn.prepareCall(sql, rsType.intValue(), ResultSet.CONCUR_READ_ONLY);
738       sessionScope.putPreparedStatement(delegate, sql, cs);
739       return cs;
740     }
741   }
742 
743   /**
744    * Prepare statement.
745    *
746    * @param sessionScope
747    *          the session scope
748    * @param conn
749    *          the conn
750    * @param sql
751    *          the sql
752    *
753    * @return the prepared statement
754    *
755    * @throws SQLException
756    *           the SQL exception
757    */
758   private static PreparedStatement prepareStatement(SessionScope sessionScope, Connection conn, String sql)
759       throws SQLException {
760     SqlMapExecutorDelegate delegate = ((SqlMapClientImpl) sessionScope.getSqlMapExecutor()).getDelegate();
761     if (sessionScope.hasPreparedStatementFor(sql)) {
762       return sessionScope.getPreparedStatement((sql));
763     } else {
764       PreparedStatement ps = conn.prepareStatement(sql);
765       sessionScope.putPreparedStatement(delegate, sql, ps);
766       return ps;
767     }
768   }
769 
770   /**
771    * Prepare call.
772    *
773    * @param sessionScope
774    *          the session scope
775    * @param conn
776    *          the conn
777    * @param sql
778    *          the sql
779    *
780    * @return the callable statement
781    *
782    * @throws SQLException
783    *           the SQL exception
784    */
785   private CallableStatement prepareCall(SessionScope sessionScope, Connection conn, String sql) throws SQLException {
786     SqlMapExecutorDelegate delegate = ((SqlMapClientImpl) sessionScope.getSqlMapExecutor()).getDelegate();
787     if (sessionScope.hasPreparedStatementFor(sql)) {
788       return (CallableStatement) sessionScope.getPreparedStatement((sql));
789     } else {
790       CallableStatement cs = conn.prepareCall(sql);
791       sessionScope.putPreparedStatement(delegate, sql, cs);
792       return cs;
793     }
794   }
795 
796   /**
797    * Close statement.
798    *
799    * @param sessionScope
800    *          the session scope
801    * @param ps
802    *          the ps
803    */
804   private static void closeStatement(SessionScope sessionScope, PreparedStatement ps) {
805     if (ps != null) {
806       if (!sessionScope.hasPreparedStatement(ps)) {
807         try {
808           ps.close();
809         } catch (SQLException e) {
810           // ignore
811         }
812       }
813     }
814   }
815 
816   /**
817    * Close result set.
818    *
819    * @param rs
820    *          the rs
821    */
822   private static void closeResultSet(ResultSet rs) {
823     if (rs != null) {
824       try {
825         rs.close();
826       } catch (SQLException e) {
827         // ignore
828       }
829     }
830   }
831 
832   /**
833    * Sets the statement timeout.
834    *
835    * @param mappedStatement
836    *          the mapped statement
837    * @param statement
838    *          the statement
839    *
840    * @throws SQLException
841    *           the SQL exception
842    */
843   private static void setStatementTimeout(MappedStatement mappedStatement, Statement statement) throws SQLException {
844     if (mappedStatement.getTimeout() != null) {
845       statement.setQueryTimeout(mappedStatement.getTimeout().intValue());
846     }
847   }
848 
849   //
850   // Inner Classes
851   //
852 
853   /**
854    * The Class Batch.
855    */
856   private static class Batch {
857 
858     /** The current sql. */
859     private String currentSql;
860 
861     /** The statement list. */
862     private List statementList = new ArrayList();
863 
864     /** The batch result list. */
865     private List batchResultList = new ArrayList();
866 
867     /** The size. */
868     private int size;
869 
870     /**
871      * Create a new batch.
872      */
873     public Batch() {
874       this.size = 0;
875     }
876 
877     /**
878      * Getter for the batch size.
879      *
880      * @return - the batch size
881      */
882     public int getSize() {
883       return size;
884     }
885 
886     /**
887      * Add a prepared statement to the batch.
888      *
889      * @param statementScope
890      *          - the request scope
891      * @param conn
892      *          - the database connection
893      * @param sql
894      *          - the SQL to add
895      * @param parameters
896      *          - the parameters for the SQL
897      *
898      * @throws SQLException
899      *           - if the prepare for the SQL fails
900      */
901     public void addBatch(StatementScope statementScope, Connection conn, String sql, Object[] parameters)
902         throws SQLException {
903       PreparedStatement ps = null;
904       if (currentSql != null && currentSql.equals(sql)) {
905         int last = statementList.size() - 1;
906         ps = (PreparedStatement) statementList.get(last);
907       } else {
908         ps = prepareStatement(statementScope.getSession(), conn, sql);
909         setStatementTimeout(statementScope.getStatement(), ps);
910         currentSql = sql;
911         statementList.add(ps);
912         batchResultList.add(new BatchResult(statementScope.getStatement().getId(), sql));
913       }
914       statementScope.getParameterMap().setParameters(statementScope, ps, parameters);
915       ps.addBatch();
916       size++;
917     }
918 
919     /**
920      * TODO (Jeff Butler) - maybe this method should be deprecated in some release, and then removed in some even later
921      * release. executeBatchDetailed gives much more complete information.
922      * <p>
923      * Execute the current session's batch
924      *
925      * @return - the number of rows updated
926      *
927      * @throws SQLException
928      *           - if the batch fails
929      */
930     public int executeBatch() throws SQLException {
931       int totalRowCount = 0;
932       for (int i = 0, n = statementList.size(); i < n; i++) {
933         PreparedStatement ps = (PreparedStatement) statementList.get(i);
934         int[] rowCounts = ps.executeBatch();
935         for (int j = 0; j < rowCounts.length; j++) {
936           if (rowCounts[j] == Statement.SUCCESS_NO_INFO) {
937             // do nothing
938           } else if (rowCounts[j] == Statement.EXECUTE_FAILED) {
939             throw new SQLException("The batched statement at index " + j + " failed to execute.");
940           } else {
941             totalRowCount += rowCounts[j];
942           }
943         }
944       }
945       return totalRowCount;
946     }
947 
948     /**
949      * Batch execution method that returns all the information the driver has to offer.
950      *
951      * @return a List of BatchResult objects
952      *
953      * @throws SQLException
954      *           if a database access error occurs, or the drive does not support batch statements
955      * @throws BatchException
956      *           if the driver throws BatchUpdateException
957      */
958     public List executeBatchDetailed() throws SQLException, BatchException {
959       List answer = new ArrayList();
960       for (int i = 0, n = statementList.size(); i < n; i++) {
961         BatchResult br = (BatchResult) batchResultList.get(i);
962         PreparedStatement ps = (PreparedStatement) statementList.get(i);
963         try {
964           br.setUpdateCounts(ps.executeBatch());
965         } catch (BatchUpdateException e) {
966           StringBuilder message = new StringBuilder();
967           message.append("Sub batch number ");
968           message.append(i + 1);
969           message.append(" failed.");
970           if (i > 0) {
971             message.append(" ");
972             message.append(i);
973             message.append(" prior sub batch(s) completed successfully, but will be rolled back.");
974           }
975           throw new BatchException(message.toString(), e, answer, br.getStatementId(), br.getSql());
976         }
977         answer.add(br);
978       }
979       return answer;
980     }
981 
982     /**
983      * Close all the statements in the batch and clear all the statements.
984      *
985      * @param sessionScope
986      *          the session scope
987      */
988     public void cleanupBatch(SessionScope sessionScope) {
989       for (int i = 0, n = statementList.size(); i < n; i++) {
990         PreparedStatement ps = (PreparedStatement) statementList.get(i);
991         closeStatement(sessionScope, ps);
992       }
993       currentSql = null;
994       statementList.clear();
995       batchResultList.clear();
996       size = 0;
997     }
998   }
999 
1000   /**
1001    * Sets the up result object factory.
1002    *
1003    * @param statementScope
1004    *          the new up result object factory
1005    */
1006   private void setupResultObjectFactory(StatementScope statementScope) {
1007     SqlMapClientImpl client = (SqlMapClientImpl) statementScope.getSession().getSqlMapClient();
1008     ResultObjectFactoryUtil.setupResultObjectFactory(client.getResultObjectFactory(),
1009         statementScope.getStatement().getId());
1010   }
1011 
1012   /**
1013    * Cleanup result object factory.
1014    */
1015   private void cleanupResultObjectFactory() {
1016     ResultObjectFactoryUtil.cleanupResultObjectFactory();
1017   }
1018 }