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