View Javadoc
1   /*
2    * SPDX-License-Identifier: Apache-2.0
3    * See LICENSE file for details.
4    *
5    * Copyright 2015-2026 the original author or authors.
6    */
7   package org.springframework.orm.ibatis;
8   
9   import com.ibatis.sqlmap.client.SqlMapClient;
10  import com.ibatis.sqlmap.client.SqlMapExecutor;
11  import com.ibatis.sqlmap.client.SqlMapSession;
12  import com.ibatis.sqlmap.client.event.RowHandler;
13  
14  import java.sql.Connection;
15  import java.sql.SQLException;
16  import java.util.List;
17  import java.util.Map;
18  
19  import javax.sql.DataSource;
20  
21  import org.springframework.dao.DataAccessException;
22  import org.springframework.jdbc.CannotGetJdbcConnectionException;
23  import org.springframework.jdbc.JdbcUpdateAffectedIncorrectNumberOfRowsException;
24  import org.springframework.jdbc.datasource.DataSourceUtils;
25  import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
26  import org.springframework.jdbc.support.JdbcAccessor;
27  import org.springframework.util.Assert;
28  
29  /**
30   * Helper class that simplifies data access via the iBATIS {@link com.ibatis.sqlmap.client.SqlMapClient} API, converting
31   * checked SQLExceptions into unchecked DataAccessExceptions, following the {@code org.springframework.dao} exception
32   * hierarchy. Uses the same {@link org.springframework.jdbc.support.SQLExceptionTranslator} mechanism as
33   * {@link org.springframework.jdbc.core.JdbcTemplate}.
34   * <p>
35   * The main method of this class executes a callback that implements a data access action. Furthermore, this class
36   * provides numerous convenience methods that mirror {@link com.ibatis.sqlmap.client.SqlMapExecutor}'s execution
37   * methods.
38   * <p>
39   * It is generally recommended to use the convenience methods on this template for plain query/insert/update/delete
40   * operations. However, for more complex operations like batch updates, a custom SqlMapClientCallback must be
41   * implemented, usually as anonymous inner class. For example:
42   *
43   * <pre class="code">
44   * getSqlMapClientTemplate().execute(new SqlMapClientCallback() {
45   *   public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
46   *     executor.startBatch();
47   *     executor.update("insertSomething", "myParamValue");
48   *     executor.update("insertSomethingElse", "myOtherParamValue");
49   *     executor.executeBatch();
50   *     return null;
51   *   }
52   * });
53   * </pre>
54   *
55   * The template needs a SqlMapClient to work on, passed in via the "sqlMapClient" property. A Spring context typically
56   * uses a {@link SqlMapClientFactoryBean} to build the SqlMapClient. The template can additionally be configured with a
57   * DataSource for fetching Connections, although this is not necessary if a DataSource is specified for the SqlMapClient
58   * itself (typically through SqlMapClientFactoryBean's "dataSource" property).
59   *
60   * @author Juergen Hoeller
61   *
62   * @since 24.02.2004
63   *
64   * @see #execute
65   * @see #setSqlMapClient
66   * @see #setDataSource
67   * @see #setExceptionTranslator
68   * @see SqlMapClientFactoryBean#setDataSource
69   * @see com.ibatis.sqlmap.client.SqlMapClient#getDataSource
70   * @see com.ibatis.sqlmap.client.SqlMapExecutor
71   *
72   * @deprecated as of Spring 3.2, in favor of the native Spring support in the Mybatis follow-up project
73   *             (https://mybatis.org/)
74   */
75  @Deprecated
76  @SuppressWarnings("rawtypes")
77  public class SqlMapClientTemplate extends JdbcAccessor implements SqlMapClientOperations {
78  
79    private SqlMapClient sqlMapClient;
80  
81    /**
82     * Create a new SqlMapClientTemplate.
83     */
84    public SqlMapClientTemplate() {
85    }
86  
87    /**
88     * Create a new SqlMapTemplate.
89     *
90     * @param sqlMapClient
91     *          iBATIS SqlMapClient that defines the mapped statements
92     */
93    public SqlMapClientTemplate(SqlMapClient sqlMapClient) {
94      setSqlMapClient(sqlMapClient);
95      afterPropertiesSet();
96    }
97  
98    /**
99     * Create a new SqlMapTemplate.
100    *
101    * @param dataSource
102    *          JDBC DataSource to obtain connections from
103    * @param sqlMapClient
104    *          iBATIS SqlMapClient that defines the mapped statements
105    */
106   public SqlMapClientTemplate(DataSource dataSource, SqlMapClient sqlMapClient) {
107     setDataSource(dataSource);
108     setSqlMapClient(sqlMapClient);
109     afterPropertiesSet();
110   }
111 
112   /**
113    * Set the iBATIS Database Layer SqlMapClient that defines the mapped statements.
114    *
115    * @param sqlMapClient
116    *          the SqlMapClient to use
117    */
118   public void setSqlMapClient(SqlMapClient sqlMapClient) {
119     this.sqlMapClient = sqlMapClient;
120   }
121 
122   /**
123    * Return the iBATIS Database Layer SqlMapClient that this template works with.
124    *
125    * @return the SqlMapClient
126    */
127   public SqlMapClient getSqlMapClient() {
128     return this.sqlMapClient;
129   }
130 
131   /**
132    * If no DataSource specified, use SqlMapClient's DataSource.
133    *
134    * @see com.ibatis.sqlmap.client.SqlMapClient#getDataSource()
135    */
136   @Override
137   public DataSource getDataSource() {
138     DataSource ds = super.getDataSource();
139     return (ds != null ? ds : this.sqlMapClient.getDataSource());
140   }
141 
142   @Override
143   public void afterPropertiesSet() {
144     if (this.sqlMapClient == null) {
145       throw new IllegalArgumentException("Property 'sqlMapClient' is required");
146     }
147     super.afterPropertiesSet();
148   }
149 
150   /**
151    * Execute the given data access action on a SqlMapExecutor.
152    *
153    * @param action
154    *          callback object that specifies the data access action
155    * @param <T>
156    *          the result type
157    *
158    * @return a result object returned by the action, or {@code null}
159    *
160    * @throws DataAccessException
161    *           in case of SQL Maps errors
162    */
163   public <T> T execute(SqlMapClientCallback<T> action) throws DataAccessException {
164     Assert.notNull(action, "Callback object must not be null");
165     Assert.notNull(this.sqlMapClient, "No SqlMapClient specified");
166 
167     // We always need to use a SqlMapSession, as we need to pass a Spring-managed
168     // Connection (potentially transactional) in. This shouldn't be necessary if
169     // we run against a TransactionAwareDataSourceProxy underneath, but unfortunately
170     // we still need it to make iBATIS batch execution work properly: If iBATIS
171     // doesn't recognize an existing transaction, it automatically executes the
172     // batch for every single statement...
173 
174     SqlMapSession session = this.sqlMapClient.openSession();
175     if (logger.isDebugEnabled()) {
176       logger.debug("Opened SqlMapSession [" + session + "] for iBATIS operation");
177     }
178     Connection ibatisCon = null;
179 
180     try {
181       Connection springCon = null;
182       DataSource dataSource = getDataSource();
183       boolean transactionAware = (dataSource instanceof TransactionAwareDataSourceProxy);
184 
185       // Obtain JDBC Connection to operate on...
186       try {
187         ibatisCon = session.getCurrentConnection();
188         if (ibatisCon == null) {
189           springCon = (transactionAware ? dataSource.getConnection() : DataSourceUtils.doGetConnection(dataSource));
190           session.setUserConnection(springCon);
191           if (logger.isDebugEnabled()) {
192             logger.debug("Obtained JDBC Connection [" + springCon + "] for iBATIS operation");
193           }
194         } else {
195           if (logger.isDebugEnabled()) {
196             logger.debug("Reusing JDBC Connection [" + ibatisCon + "] for iBATIS operation");
197           }
198         }
199       } catch (SQLException ex) {
200         throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
201       }
202 
203       // Execute given callback...
204       try {
205         return action.doInSqlMapClient(session);
206       } catch (SQLException ex) {
207         throw getExceptionTranslator().translate("SqlMapClient operation", null, ex);
208       } finally {
209         try {
210           if (springCon != null) {
211             if (transactionAware) {
212               springCon.close();
213             } else {
214               DataSourceUtils.doReleaseConnection(springCon, dataSource);
215             }
216           }
217         } catch (Throwable ex) {
218           logger.debug("Could not close JDBC Connection", ex);
219         }
220       }
221 
222       // Processing finished - potentially session still to be closed.
223     } finally {
224       // Only close SqlMapSession if we know we've actually opened it
225       // at the present level.
226       if (ibatisCon == null) {
227         session.close();
228       }
229     }
230   }
231 
232   /**
233    * Execute the given data access action on a SqlMapExecutor, expecting a List result.
234    *
235    * @param action
236    *          callback object that specifies the data access action
237    *
238    * @return the List result
239    *
240    * @throws DataAccessException
241    *           in case of SQL Maps errors
242    *
243    * @deprecated as of Spring 3.0 - not really needed anymore with generic {@link #execute} method
244    */
245   @Deprecated
246   public List executeWithListResult(SqlMapClientCallback<List> action) throws DataAccessException {
247     return execute(action);
248   }
249 
250   /**
251    * Execute the given data access action on a SqlMapExecutor, expecting a Map result.
252    *
253    * @param action
254    *          callback object that specifies the data access action
255    *
256    * @return the Map result
257    *
258    * @throws DataAccessException
259    *           in case of SQL Maps errors
260    *
261    * @deprecated as of Spring 3.0 - not really needed anymore with generic {@link #execute} method
262    */
263   @Deprecated
264   public Map executeWithMapResult(SqlMapClientCallback<Map> action) throws DataAccessException {
265     return execute(action);
266   }
267 
268   @Override
269   public Object queryForObject(String statementName) throws DataAccessException {
270     return queryForObject(statementName, null);
271   }
272 
273   @Override
274   public Object queryForObject(final String statementName, final Object parameterObject) throws DataAccessException {
275     return execute(executor -> executor.queryForObject(statementName, parameterObject));
276   }
277 
278   @Override
279   public Object queryForObject(final String statementName, final Object parameterObject, final Object resultObject)
280       throws DataAccessException {
281     return execute(executor -> executor.queryForObject(statementName, parameterObject, resultObject));
282   }
283 
284   @Override
285   public List queryForList(String statementName) throws DataAccessException {
286     return queryForList(statementName, null);
287   }
288 
289   @Override
290   public List queryForList(final String statementName, final Object parameterObject) throws DataAccessException {
291     return execute(executor -> executor.queryForList(statementName, parameterObject));
292   }
293 
294   @Override
295   public List queryForList(String statementName, int skipResults, int maxResults) throws DataAccessException {
296     return queryForList(statementName, null, skipResults, maxResults);
297   }
298 
299   @Override
300   public List queryForList(final String statementName, final Object parameterObject, final int skipResults,
301       final int maxResults) throws DataAccessException {
302     return execute(executor -> executor.queryForList(statementName, parameterObject, skipResults, maxResults));
303   }
304 
305   @Override
306   public void queryWithRowHandler(String statementName, RowHandler rowHandler) throws DataAccessException {
307     queryWithRowHandler(statementName, null, rowHandler);
308   }
309 
310   @Override
311   public void queryWithRowHandler(final String statementName, final Object parameterObject, final RowHandler rowHandler)
312       throws DataAccessException {
313     execute(executor -> {
314       executor.queryWithRowHandler(statementName, parameterObject, rowHandler);
315       return null;
316     });
317   }
318 
319   @Override
320   public Map queryForMap(final String statementName, final Object parameterObject, final String keyProperty)
321       throws DataAccessException {
322     return execute(executor -> executor.queryForMap(statementName, parameterObject, keyProperty));
323   }
324 
325   @Override
326   public Map queryForMap(final String statementName, final Object parameterObject, final String keyProperty,
327       final String valueProperty) throws DataAccessException {
328     return execute(executor -> executor.queryForMap(statementName, parameterObject, keyProperty, valueProperty));
329   }
330 
331   @Override
332   public Object insert(String statementName) throws DataAccessException {
333     return insert(statementName, null);
334   }
335 
336   @Override
337   public Object insert(final String statementName, final Object parameterObject) throws DataAccessException {
338     return execute(executor -> executor.insert(statementName, parameterObject));
339   }
340 
341   @Override
342   public int update(String statementName) throws DataAccessException {
343     return update(statementName, null);
344   }
345 
346   @Override
347   public int update(final String statementName, final Object parameterObject) throws DataAccessException {
348     Integer result = execute(executor -> executor.update(statementName, parameterObject));
349     return (result != null ? result : 0);
350   }
351 
352   @Override
353   public void update(String statementName, Object parameterObject, int requiredRowsAffected)
354       throws DataAccessException {
355     int actualRowsAffected = update(statementName, parameterObject);
356     if (actualRowsAffected != requiredRowsAffected) {
357       throw new JdbcUpdateAffectedIncorrectNumberOfRowsException(statementName, requiredRowsAffected,
358           actualRowsAffected);
359     }
360   }
361 
362   @Override
363   public int delete(String statementName) throws DataAccessException {
364     return delete(statementName, null);
365   }
366 
367   @Override
368   public int delete(final String statementName, final Object parameterObject) throws DataAccessException {
369     Integer result = execute(executor -> executor.delete(statementName, parameterObject));
370     return (result != null ? result : 0);
371   }
372 
373   @Override
374   public void delete(String statementName, Object parameterObject, int requiredRowsAffected)
375       throws DataAccessException {
376     int actualRowsAffected = delete(statementName, parameterObject);
377     if (actualRowsAffected != requiredRowsAffected) {
378       throw new JdbcUpdateAffectedIncorrectNumberOfRowsException(statementName, requiredRowsAffected,
379           actualRowsAffected);
380     }
381   }
382 
383 }