001: /*
002: * Copyright 2002-2006 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.orm.ibatis;
018:
019: import java.sql.Connection;
020: import java.sql.SQLException;
021: import java.util.List;
022: import java.util.Map;
023:
024: import javax.sql.DataSource;
025:
026: import com.ibatis.common.util.PaginatedList;
027: import com.ibatis.sqlmap.client.SqlMapClient;
028: import com.ibatis.sqlmap.client.SqlMapExecutor;
029: import com.ibatis.sqlmap.client.SqlMapSession;
030: import com.ibatis.sqlmap.client.event.RowHandler;
031: import com.ibatis.sqlmap.engine.impl.ExtendedSqlMapClient;
032:
033: import org.springframework.dao.DataAccessException;
034: import org.springframework.dao.InvalidDataAccessApiUsageException;
035: import org.springframework.jdbc.JdbcUpdateAffectedIncorrectNumberOfRowsException;
036: import org.springframework.jdbc.datasource.DataSourceUtils;
037: import org.springframework.jdbc.support.JdbcAccessor;
038: import org.springframework.util.Assert;
039:
040: /**
041: * Helper class that simplifies data access via the iBATIS
042: * {@link com.ibatis.sqlmap.client.SqlMapClient} API, converting checked
043: * SQLExceptions into unchecked DataAccessExceptions, following the
044: * <code>org.springframework.dao</code> exception hierarchy.
045: * Uses the same {@link org.springframework.jdbc.support.SQLExceptionTranslator}
046: * mechanism as {@link org.springframework.jdbc.core.JdbcTemplate}.
047: *
048: * <p>The main method of this class executes a callback that implements a
049: * data access action. Furthermore, this class provides numerous convenience
050: * methods that mirror {@link com.ibatis.sqlmap.client.SqlMapExecutor}'s
051: * execution methods.
052: *
053: * <p>It is generally recommended to use the convenience methods on this template
054: * for plain query/insert/update/delete operations. However, for more complex
055: * operations like batch updates, a custom SqlMapClientCallback must be implemented,
056: * usually as anonymous inner class. For example:
057: *
058: * <pre class="code">
059: * getSqlMapClientTemplate().execute(new SqlMapClientCallback() {
060: * public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
061: * executor.startBatch();
062: * executor.update("insertSomething", "myParamValue");
063: * executor.update("insertSomethingElse", "myOtherParamValue");
064: * executor.executeBatch();
065: * return null;
066: * }
067: * });</pre>
068: *
069: * The template needs a SqlMapClient to work on, passed in via the "sqlMapClient"
070: * property. A Spring context typically uses a {@link SqlMapClientFactoryBean}
071: * to build the SqlMapClient. The template an additionally be configured with a
072: * DataSource for fetching Connections, although this is not necessary if a
073: * DataSource is specified for the SqlMapClient itself (typically through
074: * SqlMapClientFactoryBean's "dataSource" property).
075: *
076: * @author Juergen Hoeller
077: * @since 24.02.2004
078: * @see #execute
079: * @see #setSqlMapClient
080: * @see #setDataSource
081: * @see #setExceptionTranslator
082: * @see SqlMapClientFactoryBean#setDataSource
083: * @see com.ibatis.sqlmap.client.SqlMapClient#getDataSource
084: * @see com.ibatis.sqlmap.client.SqlMapExecutor
085: */
086: public class SqlMapClientTemplate extends JdbcAccessor implements
087: SqlMapClientOperations {
088:
089: private SqlMapClient sqlMapClient;
090:
091: private boolean lazyLoadingAvailable = true;
092:
093: /**
094: * Create a new SqlMapClientTemplate.
095: */
096: public SqlMapClientTemplate() {
097: }
098:
099: /**
100: * Create a new SqlMapTemplate.
101: * @param sqlMapClient iBATIS SqlMapClient that defines the mapped statements
102: */
103: public SqlMapClientTemplate(SqlMapClient sqlMapClient) {
104: setSqlMapClient(sqlMapClient);
105: afterPropertiesSet();
106: }
107:
108: /**
109: * Create a new SqlMapTemplate.
110: * @param dataSource JDBC DataSource to obtain connections from
111: * @param sqlMapClient iBATIS SqlMapClient that defines the mapped statements
112: */
113: public SqlMapClientTemplate(DataSource dataSource,
114: SqlMapClient sqlMapClient) {
115: setDataSource(dataSource);
116: setSqlMapClient(sqlMapClient);
117: afterPropertiesSet();
118: }
119:
120: /**
121: * Set the iBATIS Database Layer SqlMapClient that defines the mapped statements.
122: */
123: public void setSqlMapClient(SqlMapClient sqlMapClient) {
124: this .sqlMapClient = sqlMapClient;
125: }
126:
127: /**
128: * Return the iBATIS Database Layer SqlMapClient that this template works with.
129: */
130: public SqlMapClient getSqlMapClient() {
131: return sqlMapClient;
132: }
133:
134: /**
135: * If no DataSource specified, use SqlMapClient's DataSource.
136: * @see com.ibatis.sqlmap.client.SqlMapClient#getDataSource
137: */
138: public DataSource getDataSource() {
139: DataSource ds = super .getDataSource();
140: return (ds != null ? ds : this .sqlMapClient.getDataSource());
141: }
142:
143: public void afterPropertiesSet() {
144: if (this .sqlMapClient == null) {
145: throw new IllegalArgumentException(
146: "sqlMapClient is required");
147: }
148: if (this .sqlMapClient instanceof ExtendedSqlMapClient) {
149: // Check whether iBATIS lazy loading is available, that is,
150: // whether a DataSource was specified on the SqlMapClient itself.
151: this .lazyLoadingAvailable = (((ExtendedSqlMapClient) this .sqlMapClient)
152: .getDelegate().getTxManager() != null);
153: }
154: super .afterPropertiesSet();
155: }
156:
157: /**
158: * Execute the given data access action on a SqlMapExecutor.
159: * @param action callback object that specifies the data access action
160: * @return a result object returned by the action, or <code>null</code>
161: * @throws DataAccessException in case of SQL Maps errors
162: */
163: public Object execute(SqlMapClientCallback action)
164: throws DataAccessException {
165: Assert.notNull(action, "Callback object must not be null");
166: Assert.notNull(this .sqlMapClient, "No SqlMapClient specified");
167:
168: // We always needs to use a SqlMapSession, as we need to pass a Spring-managed
169: // Connection (potentially transactional) in. This shouldn't be necessary if
170: // we run against a TransactionAwareDataSourceProxy underneath, but unfortunately
171: // we still need it to make iBATIS batch execution work properly: If iBATIS
172: // doesn't recognize an existing transaction, it automatically executes the
173: // batch for every single statement...
174:
175: SqlMapSession session = this .sqlMapClient.openSession();
176: Connection ibatisCon = null;
177: try {
178: if (logger.isDebugEnabled()) {
179: logger.debug("Opened SqlMapSession [" + session
180: + "] for iBATIS operation");
181: }
182: Connection springCon = null;
183: try {
184: ibatisCon = session.getCurrentConnection();
185: if (ibatisCon == null) {
186: springCon = DataSourceUtils
187: .getConnection(getDataSource());
188: session.setUserConnection(springCon);
189: if (logger.isDebugEnabled()) {
190: logger.debug("Obtained JDBC Connection ["
191: + springCon + "] for iBATIS operation");
192: }
193: } else {
194: if (logger.isDebugEnabled()) {
195: logger.debug("Reusing JDBC Connection ["
196: + ibatisCon + "] for iBATIS operation");
197: }
198: }
199: return action.doInSqlMapClient(session);
200: } catch (SQLException ex) {
201: throw getExceptionTranslator().translate(
202: "SqlMapClient operation", null, ex);
203: } finally {
204: DataSourceUtils.releaseConnection(springCon,
205: getDataSource());
206: }
207: } finally {
208: // Only close SqlMapSession if we know we've actually opened it
209: // at the present level.
210: if (ibatisCon == null) {
211: session.close();
212: }
213: }
214: }
215:
216: /**
217: * Execute the given data access action on a SqlMapExecutor,
218: * expecting a List result.
219: * @param action callback object that specifies the data access action
220: * @return the List result
221: * @throws DataAccessException in case of SQL Maps errors
222: */
223: public List executeWithListResult(SqlMapClientCallback action)
224: throws DataAccessException {
225: return (List) execute(action);
226: }
227:
228: /**
229: * Execute the given data access action on a SqlMapExecutor,
230: * expecting a Map result.
231: * @param action callback object that specifies the data access action
232: * @return the Map result
233: * @throws DataAccessException in case of SQL Maps errors
234: */
235: public Map executeWithMapResult(SqlMapClientCallback action)
236: throws DataAccessException {
237: return (Map) execute(action);
238: }
239:
240: public Object queryForObject(String statementName)
241: throws DataAccessException {
242: return queryForObject(statementName, null);
243: }
244:
245: public Object queryForObject(final String statementName,
246: final Object parameterObject) throws DataAccessException {
247:
248: return execute(new SqlMapClientCallback() {
249: public Object doInSqlMapClient(SqlMapExecutor executor)
250: throws SQLException {
251: return executor.queryForObject(statementName,
252: parameterObject);
253: }
254: });
255: }
256:
257: public Object queryForObject(final String statementName,
258: final Object parameterObject, final Object resultObject)
259: throws DataAccessException {
260:
261: return execute(new SqlMapClientCallback() {
262: public Object doInSqlMapClient(SqlMapExecutor executor)
263: throws SQLException {
264: return executor.queryForObject(statementName,
265: parameterObject, resultObject);
266: }
267: });
268: }
269:
270: public List queryForList(String statementName)
271: throws DataAccessException {
272: return queryForList(statementName, null);
273: }
274:
275: public List queryForList(final String statementName,
276: final Object parameterObject) throws DataAccessException {
277:
278: return executeWithListResult(new SqlMapClientCallback() {
279: public Object doInSqlMapClient(SqlMapExecutor executor)
280: throws SQLException {
281: return executor.queryForList(statementName,
282: parameterObject);
283: }
284: });
285: }
286:
287: public List queryForList(String statementName, int skipResults,
288: int maxResults) throws DataAccessException {
289:
290: return queryForList(statementName, null, skipResults,
291: maxResults);
292: }
293:
294: public List queryForList(final String statementName,
295: final Object parameterObject, final int skipResults,
296: final int maxResults) throws DataAccessException {
297:
298: return executeWithListResult(new SqlMapClientCallback() {
299: public Object doInSqlMapClient(SqlMapExecutor executor)
300: throws SQLException {
301: return executor.queryForList(statementName,
302: parameterObject, skipResults, maxResults);
303: }
304: });
305: }
306:
307: public void queryWithRowHandler(String statementName,
308: RowHandler rowHandler) throws DataAccessException {
309:
310: queryWithRowHandler(statementName, null, rowHandler);
311: }
312:
313: public void queryWithRowHandler(final String statementName,
314: final Object parameterObject, final RowHandler rowHandler)
315: throws DataAccessException {
316:
317: execute(new SqlMapClientCallback() {
318: public Object doInSqlMapClient(SqlMapExecutor executor)
319: throws SQLException {
320: executor.queryWithRowHandler(statementName,
321: parameterObject, rowHandler);
322: return null;
323: }
324: });
325: }
326:
327: /**
328: * @deprecated as of iBATIS 2.3.0
329: */
330: public PaginatedList queryForPaginatedList(String statementName,
331: int pageSize) throws DataAccessException {
332:
333: return queryForPaginatedList(statementName, null, pageSize);
334: }
335:
336: /**
337: * @deprecated as of iBATIS 2.3.0
338: */
339: public PaginatedList queryForPaginatedList(
340: final String statementName, final Object parameterObject,
341: final int pageSize) throws DataAccessException {
342:
343: // Throw exception if lazy loading will not work.
344: if (!this .lazyLoadingAvailable) {
345: throw new InvalidDataAccessApiUsageException(
346: "SqlMapClient needs to have DataSource to allow for lazy loading"
347: + " - specify SqlMapClientFactoryBean's 'dataSource' property");
348: }
349:
350: return (PaginatedList) execute(new SqlMapClientCallback() {
351: public Object doInSqlMapClient(SqlMapExecutor executor)
352: throws SQLException {
353: return executor.queryForPaginatedList(statementName,
354: parameterObject, pageSize);
355: }
356: });
357: }
358:
359: public Map queryForMap(final String statementName,
360: final Object parameterObject, final String keyProperty)
361: throws DataAccessException {
362:
363: return executeWithMapResult(new SqlMapClientCallback() {
364: public Object doInSqlMapClient(SqlMapExecutor executor)
365: throws SQLException {
366: return executor.queryForMap(statementName,
367: parameterObject, keyProperty);
368: }
369: });
370: }
371:
372: public Map queryForMap(final String statementName,
373: final Object parameterObject, final String keyProperty,
374: final String valueProperty) throws DataAccessException {
375:
376: return executeWithMapResult(new SqlMapClientCallback() {
377: public Object doInSqlMapClient(SqlMapExecutor executor)
378: throws SQLException {
379: return executor.queryForMap(statementName,
380: parameterObject, keyProperty, valueProperty);
381: }
382: });
383: }
384:
385: public Object insert(String statementName)
386: throws DataAccessException {
387: return insert(statementName, null);
388: }
389:
390: public Object insert(final String statementName,
391: final Object parameterObject) throws DataAccessException {
392:
393: return execute(new SqlMapClientCallback() {
394: public Object doInSqlMapClient(SqlMapExecutor executor)
395: throws SQLException {
396: return executor.insert(statementName, parameterObject);
397: }
398: });
399: }
400:
401: public int update(String statementName) throws DataAccessException {
402: return update(statementName, null);
403: }
404:
405: public int update(final String statementName,
406: final Object parameterObject) throws DataAccessException {
407:
408: Integer result = (Integer) execute(new SqlMapClientCallback() {
409: public Object doInSqlMapClient(SqlMapExecutor executor)
410: throws SQLException {
411: return new Integer(executor.update(statementName,
412: parameterObject));
413: }
414: });
415: return result.intValue();
416: }
417:
418: public void update(String statementName, Object parameterObject,
419: int requiredRowsAffected) throws DataAccessException {
420:
421: int actualRowsAffected = update(statementName, parameterObject);
422: if (actualRowsAffected != requiredRowsAffected) {
423: throw new JdbcUpdateAffectedIncorrectNumberOfRowsException(
424: statementName, requiredRowsAffected,
425: actualRowsAffected);
426: }
427: }
428:
429: public int delete(String statementName) throws DataAccessException {
430: return delete(statementName, null);
431: }
432:
433: public int delete(final String statementName,
434: final Object parameterObject) throws DataAccessException {
435:
436: Integer result = (Integer) execute(new SqlMapClientCallback() {
437: public Object doInSqlMapClient(SqlMapExecutor executor)
438: throws SQLException {
439: return new Integer(executor.delete(statementName,
440: parameterObject));
441: }
442: });
443: return result.intValue();
444: }
445:
446: public void delete(String statementName, Object parameterObject,
447: int requiredRowsAffected) throws DataAccessException {
448:
449: int actualRowsAffected = delete(statementName, parameterObject);
450: if (actualRowsAffected != requiredRowsAffected) {
451: throw new JdbcUpdateAffectedIncorrectNumberOfRowsException(
452: statementName, requiredRowsAffected,
453: actualRowsAffected);
454: }
455: }
456:
457: }
|