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