DataSourceTransactionManager.java
Upload User: jiancairen
Upload Date: 2007-08-27
Package Size: 26458k
Code Size: 10k
Category:

Java Develop

Development Platform:

Java

  1. /*
  2.  * Copyright 2002-2004 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. package org.springframework.jdbc.datasource;
  17. import java.sql.Connection;
  18. import java.sql.SQLException;
  19. import javax.sql.DataSource;
  20. import org.springframework.beans.factory.InitializingBean;
  21. import org.springframework.transaction.CannotCreateTransactionException;
  22. import org.springframework.transaction.TransactionDefinition;
  23. import org.springframework.transaction.TransactionException;
  24. import org.springframework.transaction.TransactionSystemException;
  25. import org.springframework.transaction.support.AbstractPlatformTransactionManager;
  26. import org.springframework.transaction.support.DefaultTransactionStatus;
  27. import org.springframework.transaction.support.TransactionSynchronizationManager;
  28. /**
  29.  * PlatformTransactionManager implementation for a single JDBC DataSource.
  30.  * Binds a JDBC connection from the specified DataSource to the thread,
  31.  * potentially allowing for one thread connection per data source.
  32.  *
  33.  * <p>Application code is required to retrieve the JDBC Connection via
  34.  * <code>DataSourceUtils.getConnection(DataSource)</code> instead of J2EE's standard
  35.  * <code>DataSource.getConnection()</code>. This is recommended anyway, as it throws
  36.  * unchecked org.springframework.dao exceptions instead of checked SQLException.
  37.  * All framework classes like JdbcTemplate use this strategy implicitly.
  38.  * If not used with this transaction manager, the lookup strategy behaves exactly
  39.  * like the common one - it can thus be used in any case.
  40.  *
  41.  * <p>Alternatively, you can also allow application code to work with the standard
  42.  * J2EE lookup pattern <code>DataSource.getConnection()</code>, for example for
  43.  * legacy code that is not aware of Spring at all. In that case, define a
  44.  * TransactionAwareDataSourceProxy for your target DataSource, and pass that proxy
  45.  * DataSource to your DAOs, which will automatically participate in Spring-managed
  46.  * transactions through it. Note that DataSourceTransactionManager still needs to
  47.  * be wired with the target DataSource, driving transactions for it.
  48.  *
  49.  * <p>Supports custom isolation levels, and timeouts that get applied as
  50.  * appropriate JDBC statement query timeouts. To support the latter,
  51.  * application code must either use JdbcTemplate or call
  52.  * <code>DataSourceUtils.applyTransactionTimeout</code> for each created statement.
  53.  *
  54.  * <p>On JDBC 3.0, this transaction manager supports nested transactions via JDBC
  55.  * 3.0 Savepoints. The "nestedTransactionAllowed" flag defaults to true, as nested
  56.  * transactions work without restrictions on JDBC drivers that support Savepoints
  57.  * (like Oracle).
  58.  *
  59.  * <p>This implementation can be used instead of JtaTransactionManager in the single
  60.  * resource case, as it does not require the container to support JTA: typically,
  61.  * in combination with a locally defined JDBC DataSource like a Jakarta Commons DBCP
  62.  * connection pool. Switching between this local strategy and a JTA environment is
  63.  * just a matter of configuration, if you stick to the required connection lookup
  64.  * pattern. Note that JTA does not support custom isolation levels!
  65.  *
  66.  * @author Juergen Hoeller
  67.  * @since 02.05.2003
  68.  * @see #setNestedTransactionAllowed
  69.  * @see java.sql.Savepoint
  70.  * @see DataSourceUtils#getConnection
  71.  * @see DataSourceUtils#applyTransactionTimeout
  72.  * @see DataSourceUtils#closeConnectionIfNecessary
  73.  * @see TransactionAwareDataSourceProxy
  74.  * @see org.springframework.jdbc.core.JdbcTemplate
  75.  */
  76. public class DataSourceTransactionManager extends AbstractPlatformTransactionManager implements InitializingBean {
  77. private DataSource dataSource;
  78. /**
  79.  * Create a new DataSourceTransactionManager instance.
  80.  * A DataSource has to be set to be able to use it.
  81.  * @see #setDataSource
  82.  */
  83. public DataSourceTransactionManager() {
  84. setNestedTransactionAllowed(true);
  85. }
  86. /**
  87.  * Create a new DataSourceTransactionManager instance.
  88.  * @param dataSource JDBC DataSource to manage transactions for
  89.  */
  90. public DataSourceTransactionManager(DataSource dataSource) {
  91. this();
  92. this.dataSource = dataSource;
  93. afterPropertiesSet();
  94. }
  95. /**
  96.  * Set the JDBC DataSource that this instance should manage transactions for.
  97.  * <p>This will typically be a locally defined DataSource, for example a
  98.  * Jakarta Commons DBCP connection pool. Alternatively, you can also drive
  99.  * transactions for a non-XA J2EE DataSource fetched from JNDI. For an XA
  100.  * DataSource, use JtaTransactionManager.
  101.  * @see org.springframework.transaction.jta.JtaTransactionManager
  102.  */
  103. public void setDataSource(DataSource dataSource) {
  104. this.dataSource = dataSource;
  105. }
  106. /**
  107.  * Return the JDBC DataSource that this instance manages transactions for.
  108.  */
  109. public DataSource getDataSource() {
  110. return dataSource;
  111. }
  112. public void afterPropertiesSet() {
  113. if (this.dataSource == null) {
  114. throw new IllegalArgumentException("dataSource is required");
  115. }
  116. }
  117. protected Object doGetTransaction() {
  118. DataSourceTransactionObject txObject = new DataSourceTransactionObject();
  119. txObject.setSavepointAllowed(isNestedTransactionAllowed());
  120. ConnectionHolder conHolder =
  121.     (ConnectionHolder) TransactionSynchronizationManager.getResource(this.dataSource);
  122. txObject.setConnectionHolder(conHolder);
  123. return txObject;
  124. }
  125. protected boolean isExistingTransaction(Object transaction) {
  126. DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
  127. // consider a pre-bound Connection as transaction
  128. return (txObject.getConnectionHolder() != null);
  129. }
  130. /**
  131.  * This implementation sets the isolation level but ignores the timeout.
  132.  */
  133. protected void doBegin(Object transaction, TransactionDefinition definition) {
  134. DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
  135. if (logger.isDebugEnabled()) {
  136. logger.debug("Opening new connection for JDBC transaction");
  137. }
  138. Connection con = DataSourceUtils.getConnection(this.dataSource, false);
  139. txObject.setConnectionHolder(new ConnectionHolder(con));
  140. txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
  141. try {
  142. Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
  143. txObject.setPreviousIsolationLevel(previousIsolationLevel);
  144. // Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
  145. // so we don't want to do it unnecessarily (for example if we're configured
  146. // Commons DBCP to set it already).
  147. if (con.getAutoCommit()) {
  148. txObject.setMustRestoreAutoCommit(true);
  149. if (logger.isDebugEnabled()) {
  150. logger.debug("Switching JDBC connection [" + con + "] to manual commit");
  151. }
  152. con.setAutoCommit(false);
  153. }
  154. if (definition.getTimeout() != TransactionDefinition.TIMEOUT_DEFAULT) {
  155. txObject.getConnectionHolder().setTimeoutInSeconds(definition.getTimeout());
  156. }
  157. TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
  158. }
  159. catch (SQLException ex) {
  160. DataSourceUtils.closeConnectionIfNecessary(con, this.dataSource);
  161. throw new CannotCreateTransactionException("Could not configure JDBC connection for transaction", ex);
  162. }
  163. }
  164. protected Object doSuspend(Object transaction) {
  165. DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
  166. txObject.setConnectionHolder(null);
  167. return TransactionSynchronizationManager.unbindResource(this.dataSource);
  168. }
  169. protected void doResume(Object transaction, Object suspendedResources) {
  170. ConnectionHolder conHolder = (ConnectionHolder) suspendedResources;
  171. TransactionSynchronizationManager.bindResource(this.dataSource, conHolder);
  172. }
  173. protected void doCommit(DefaultTransactionStatus status) {
  174. DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
  175. Connection con = txObject.getConnectionHolder().getConnection();
  176. if (status.isDebug()) {
  177. logger.debug("Committing JDBC transaction on connection [" + con + "]");
  178. }
  179. try {
  180. con.commit();
  181. }
  182. catch (SQLException ex) {
  183. throw new TransactionSystemException("Could not commit JDBC transaction", ex);
  184. }
  185. }
  186. protected void doRollback(DefaultTransactionStatus status) {
  187. DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
  188. Connection con = txObject.getConnectionHolder().getConnection();
  189. if (status.isDebug()) {
  190. logger.debug("Rolling back JDBC transaction on connection [" + con + "]");
  191. }
  192. try {
  193. con.rollback();
  194. }
  195. catch (SQLException ex) {
  196. throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
  197. }
  198. }
  199. protected void doSetRollbackOnly(DefaultTransactionStatus status) {
  200. DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
  201. if (status.isDebug()) {
  202. logger.debug("Setting JDBC transaction [" + txObject.getConnectionHolder().getConnection() +
  203.  "] rollback-only");
  204. }
  205. txObject.getConnectionHolder().setRollbackOnly();
  206. }
  207. protected void doCleanupAfterCompletion(Object transaction) {
  208. DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
  209. // remove the connection holder from the thread
  210. TransactionSynchronizationManager.unbindResource(this.dataSource);
  211. txObject.getConnectionHolder().clear();
  212. // reset connection
  213. Connection con = txObject.getConnectionHolder().getConnection();
  214. try {
  215. if (txObject.getMustRestoreAutoCommit()) {
  216. con.setAutoCommit(true);
  217. }
  218. DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel());
  219. }
  220. catch (SQLException ex) {
  221. logger.info("Could not reset JDBC connection after transaction", ex);
  222. }
  223. if (logger.isDebugEnabled()) {
  224. logger.debug("Closing JDBC connection [" + con + "] after transaction");
  225. }
  226. DataSourceUtils.closeConnectionIfNecessary(con, this.dataSource);
  227. }
  228. }