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