Save This Page
Home » spring-framework-2.5.6-with-dependencies » org.springframework » jdbc » datasource » [javadoc | source]
    1   /*
    2    * Copyright 2002-2008 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.jdbc.datasource;
   18   
   19   import java.sql.Connection;
   20   import java.sql.SQLException;
   21   import java.sql.Statement;
   22   
   23   import javax.sql.DataSource;
   24   
   25   import org.apache.commons.logging.Log;
   26   import org.apache.commons.logging.LogFactory;
   27   
   28   import org.springframework.jdbc.CannotGetJdbcConnectionException;
   29   import org.springframework.transaction.TransactionDefinition;
   30   import org.springframework.transaction.support.TransactionSynchronizationAdapter;
   31   import org.springframework.transaction.support.TransactionSynchronizationManager;
   32   import org.springframework.util.Assert;
   33    
   34   /**
   35    * Helper class that provides static methods for obtaining JDBC Connections from
   36    * a {@link javax.sql.DataSource}. Includes special support for Spring-managed
   37    * transactional Connections, e.g. managed by {@link DataSourceTransactionManager}
   38    * or {@link org.springframework.transaction.jta.JtaTransactionManager}.
   39    *
   40    * <p>Used internally by Spring's {@link org.springframework.jdbc.core.JdbcTemplate},
   41    * Spring's JDBC operation objects and the JDBC {@link DataSourceTransactionManager}.
   42    * Can also be used directly in application code.
   43    *
   44    * @author Rod Johnson
   45    * @author Juergen Hoeller
   46    * @see #getConnection
   47    * @see #releaseConnection
   48    * @see DataSourceTransactionManager
   49    * @see org.springframework.transaction.jta.JtaTransactionManager
   50    * @see org.springframework.transaction.support.TransactionSynchronizationManager
   51    */
   52   public abstract class DataSourceUtils {
   53   
   54   	/**
   55   	 * Order value for TransactionSynchronization objects that clean up
   56   	 * JDBC Connections.
   57   	 */
   58   	public static final int CONNECTION_SYNCHRONIZATION_ORDER = 1000;
   59   
   60   	private static final Log logger = LogFactory.getLog(DataSourceUtils.class);
   61   
   62   
   63   	/**
   64   	 * Obtain a Connection from the given DataSource. Translates SQLExceptions into
   65   	 * the Spring hierarchy of unchecked generic data access exceptions, simplifying
   66   	 * calling code and making any exception that is thrown more meaningful.
   67   	 * <p>Is aware of a corresponding Connection bound to the current thread, for example
   68   	 * when using {@link DataSourceTransactionManager}. Will bind a Connection to the
   69   	 * thread if transaction synchronization is active, e.g. when running within a
   70   	 * {@link org.springframework.transaction.jta.JtaTransactionManager JTA} transaction).
   71   	 * @param dataSource the DataSource to obtain Connections from
   72   	 * @return a JDBC Connection from the given DataSource
   73   	 * @throws org.springframework.jdbc.CannotGetJdbcConnectionException
   74   	 * if the attempt to get a Connection failed
   75   	 * @see #releaseConnection
   76   	 */
   77   	public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
   78   		try {
   79   			return doGetConnection(dataSource);
   80   		}
   81   		catch (SQLException ex) {
   82   			throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
   83   		}
   84   	}
   85   
   86   	/**
   87   	 * Actually obtain a JDBC Connection from the given DataSource.
   88   	 * Same as {@link #getConnection}, but throwing the original SQLException.
   89   	 * <p>Is aware of a corresponding Connection bound to the current thread, for example
   90   	 * when using {@link DataSourceTransactionManager}. Will bind a Connection to the thread
   91   	 * if transaction synchronization is active (e.g. if in a JTA transaction).
   92   	 * <p>Directly accessed by {@link TransactionAwareDataSourceProxy}.
   93   	 * @param dataSource the DataSource to obtain Connections from
   94   	 * @return a JDBC Connection from the given DataSource
   95   	 * @throws SQLException if thrown by JDBC methods
   96   	 * @see #doReleaseConnection
   97   	 */
   98   	public static Connection doGetConnection(DataSource dataSource) throws SQLException {
   99   		Assert.notNull(dataSource, "No DataSource specified");
  100   
  101   		ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
  102   		if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
  103   			conHolder.requested();
  104   			if (!conHolder.hasConnection()) {
  105   				logger.debug("Fetching resumed JDBC Connection from DataSource");
  106   				conHolder.setConnection(dataSource.getConnection());
  107   			}
  108   			return conHolder.getConnection();
  109   		}
  110   		// Else we either got no holder or an empty thread-bound holder here.
  111   
  112   		logger.debug("Fetching JDBC Connection from DataSource");
  113   		Connection con = dataSource.getConnection();
  114   
  115   		if (TransactionSynchronizationManager.isSynchronizationActive()) {
  116   			logger.debug("Registering transaction synchronization for JDBC Connection");
  117   			// Use same Connection for further JDBC actions within the transaction.
  118   			// Thread-bound object will get removed by synchronization at transaction completion.
  119   			ConnectionHolder holderToUse = conHolder;
  120   			if (holderToUse == null) {
  121   				holderToUse = new ConnectionHolder(con);
  122   			}
  123   			else {
  124   				holderToUse.setConnection(con);
  125   			}
  126   			holderToUse.requested();
  127   			TransactionSynchronizationManager.registerSynchronization(
  128   					new ConnectionSynchronization(holderToUse, dataSource));
  129   			holderToUse.setSynchronizedWithTransaction(true);
  130   			if (holderToUse != conHolder) {
  131   				TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
  132   			}
  133   		}
  134   
  135   		return con;
  136   	}
  137   
  138   	/**
  139   	 * Prepare the given Connection with the given transaction semantics.
  140   	 * @param con the Connection to prepare
  141   	 * @param definition the transaction definition to apply
  142   	 * @return the previous isolation level, if any
  143   	 * @throws SQLException if thrown by JDBC methods
  144   	 * @see #resetConnectionAfterTransaction
  145   	 */
  146   	public static Integer prepareConnectionForTransaction(Connection con, TransactionDefinition definition)
  147   			throws SQLException {
  148   
  149   		Assert.notNull(con, "No Connection specified");
  150   
  151   		// Set read-only flag.
  152   		if (definition != null && definition.isReadOnly()) {
  153   			try {
  154   				if (logger.isDebugEnabled()) {
  155   					logger.debug("Setting JDBC Connection [" + con + "] read-only");
  156   				}
  157   				con.setReadOnly(true);
  158   			}
  159   			catch (Throwable ex) {
  160   				// SQLException or UnsupportedOperationException
  161   				// -> ignore, it's just a hint anyway.
  162   				logger.debug("Could not set JDBC Connection read-only", ex);
  163   			}
  164   		}
  165   
  166   		// Apply specific isolation level, if any.
  167   		Integer previousIsolationLevel = null;
  168   		if (definition != null && definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
  169   			if (logger.isDebugEnabled()) {
  170   				logger.debug("Changing isolation level of JDBC Connection [" + con + "] to " +
  171   						definition.getIsolationLevel());
  172   			}
  173   			previousIsolationLevel = new Integer(con.getTransactionIsolation());
  174   			con.setTransactionIsolation(definition.getIsolationLevel());
  175   		}
  176   
  177   		return previousIsolationLevel;
  178   	}
  179   
  180   	/**
  181   	 * Reset the given Connection after a transaction,
  182   	 * regarding read-only flag and isolation level.
  183   	 * @param con the Connection to reset
  184   	 * @param previousIsolationLevel the isolation level to restore, if any
  185   	 * @see #prepareConnectionForTransaction
  186   	 */
  187   	public static void resetConnectionAfterTransaction(Connection con, Integer previousIsolationLevel) {
  188   		Assert.notNull(con, "No Connection specified");
  189   		try {
  190   			// Reset transaction isolation to previous value, if changed for the transaction.
  191   			if (previousIsolationLevel != null) {
  192   				if (logger.isDebugEnabled()) {
  193   					logger.debug("Resetting isolation level of JDBC Connection [" +
  194   							con + "] to " + previousIsolationLevel);
  195   				}
  196   				con.setTransactionIsolation(previousIsolationLevel.intValue());
  197   			}
  198   
  199   			// Reset read-only flag.
  200   			if (con.isReadOnly()) {
  201   				if (logger.isDebugEnabled()) {
  202   					logger.debug("Resetting read-only flag of JDBC Connection [" + con + "]");
  203   				}
  204   				con.setReadOnly(false);
  205   			}
  206   		}
  207   		catch (Throwable ex) {
  208   			logger.debug("Could not reset JDBC Connection after transaction", ex);
  209   		}
  210   	}
  211   
  212   	/**
  213   	 * Determine whether the given JDBC Connection is transactional, that is,
  214   	 * bound to the current thread by Spring's transaction facilities.
  215   	 * @param con the Connection to check
  216   	 * @param dataSource the DataSource that the Connection was obtained from
  217   	 * (may be <code>null</code>)
  218   	 * @return whether the Connection is transactional
  219   	 */
  220   	public static boolean isConnectionTransactional(Connection con, DataSource dataSource) {
  221   		if (dataSource == null) {
  222   			return false;
  223   		}
  224   		ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
  225   		return (conHolder != null && connectionEquals(conHolder, con));
  226   	}
  227   
  228   	/**
  229   	 * Apply the current transaction timeout, if any,
  230   	 * to the given JDBC Statement object.
  231   	 * @param stmt the JDBC Statement object
  232   	 * @param dataSource the DataSource that the Connection was obtained from
  233   	 * @throws SQLException if thrown by JDBC methods
  234   	 * @see java.sql.Statement#setQueryTimeout
  235   	 */
  236   	public static void applyTransactionTimeout(Statement stmt, DataSource dataSource) throws SQLException {
  237   		applyTimeout(stmt, dataSource, 0);
  238   	}
  239   
  240   	/**
  241   	 * Apply the specified timeout - overridden by the current transaction timeout,
  242   	 * if any - to the given JDBC Statement object.
  243   	 * @param stmt the JDBC Statement object
  244   	 * @param dataSource the DataSource that the Connection was obtained from
  245   	 * @param timeout the timeout to apply (or 0 for no timeout outside of a transaction)
  246   	 * @throws SQLException if thrown by JDBC methods
  247   	 * @see java.sql.Statement#setQueryTimeout
  248   	 */
  249   	public static void applyTimeout(Statement stmt, DataSource dataSource, int timeout) throws SQLException {
  250   		Assert.notNull(stmt, "No Statement specified");
  251   		Assert.notNull(dataSource, "No DataSource specified");
  252   		ConnectionHolder holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
  253   		if (holder != null && holder.hasTimeout()) {
  254   			// Remaining transaction timeout overrides specified value.
  255   			stmt.setQueryTimeout(holder.getTimeToLiveInSeconds());
  256   		}
  257   		else if (timeout > 0) {
  258   			// No current transaction timeout -> apply specified value.
  259   			stmt.setQueryTimeout(timeout);
  260   		}
  261   	}
  262   
  263   	/**
  264   	 * Close the given Connection, obtained from the given DataSource,
  265   	 * if it is not managed externally (that is, not bound to the thread).
  266   	 * @param con the Connection to close if necessary
  267   	 * (if this is <code>null</code>, the call will be ignored)
  268   	 * @param dataSource the DataSource that the Connection was obtained from
  269   	 * (may be <code>null</code>)
  270   	 * @see #getConnection
  271   	 */
  272   	public static void releaseConnection(Connection con, DataSource dataSource) {
  273   		try {
  274   			doReleaseConnection(con, dataSource);
  275   		}
  276   		catch (SQLException ex) {
  277   			logger.debug("Could not close JDBC Connection", ex);
  278   		}
  279   		catch (Throwable ex) {
  280   			logger.debug("Unexpected exception on closing JDBC Connection", ex);
  281   		}
  282   	}
  283   
  284   	/**
  285   	 * Actually close the given Connection, obtained from the given DataSource.
  286   	 * Same as {@link #releaseConnection}, but throwing the original SQLException.
  287   	 * <p>Directly accessed by {@link TransactionAwareDataSourceProxy}.
  288   	 * @param con the Connection to close if necessary
  289   	 * (if this is <code>null</code>, the call will be ignored)
  290   	 * @param dataSource the DataSource that the Connection was obtained from
  291   	 * (may be <code>null</code>)
  292   	 * @throws SQLException if thrown by JDBC methods
  293   	 * @see #doGetConnection
  294   	 */
  295   	public static void doReleaseConnection(Connection con, DataSource dataSource) throws SQLException {
  296   		if (con == null) {
  297   			return;
  298   		}
  299   
  300   		if (dataSource != null) {
  301   			ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
  302   			if (conHolder != null && connectionEquals(conHolder, con)) {
  303   				// It's the transactional Connection: Don't close it.
  304   				conHolder.released();
  305   				return;
  306   			}
  307   		}
  308   
  309   		// Leave the Connection open only if the DataSource is our
  310   		// special SmartDataSoruce and it wants the Connection left open.
  311   		if (!(dataSource instanceof SmartDataSource) || ((SmartDataSource) dataSource).shouldClose(con)) {
  312   			logger.debug("Returning JDBC Connection to DataSource");
  313   			con.close();
  314   		}
  315   	}
  316   
  317   	/**
  318   	 * Determine whether the given two Connections are equal, asking the target
  319   	 * Connection in case of a proxy. Used to detect equality even if the
  320   	 * user passed in a raw target Connection while the held one is a proxy.
  321   	 * @param conHolder the ConnectionHolder for the held Connection (potentially a proxy)
  322   	 * @param passedInCon the Connection passed-in by the user
  323   	 * (potentially a target Connection without proxy)
  324   	 * @return whether the given Connections are equal
  325   	 * @see #getTargetConnection
  326   	 */
  327   	private static boolean connectionEquals(ConnectionHolder conHolder, Connection passedInCon) {
  328   		if (!conHolder.hasConnection()) {
  329   			return false;
  330   		}
  331   		Connection heldCon = conHolder.getConnection();
  332   		// Explicitly check for identity too: for Connection handles that do not implement
  333   		// "equals" properly, such as the ones Commons DBCP exposes).
  334   		return (heldCon == passedInCon || heldCon.equals(passedInCon) ||
  335   				getTargetConnection(heldCon).equals(passedInCon));
  336   	}
  337   
  338   	/**
  339   	 * Return the innermost target Connection of the given Connection. If the given
  340   	 * Connection is a proxy, it will be unwrapped until a non-proxy Connection is
  341   	 * found. Otherwise, the passed-in Connection will be returned as-is.
  342   	 * @param con the Connection proxy to unwrap
  343   	 * @return the innermost target Connection, or the passed-in one if no proxy
  344   	 * @see ConnectionProxy#getTargetConnection()
  345   	 */
  346   	public static Connection getTargetConnection(Connection con) {
  347   		Connection conToUse = con;
  348   		while (conToUse instanceof ConnectionProxy) {
  349   			conToUse = ((ConnectionProxy) conToUse).getTargetConnection();
  350   		}
  351   		return conToUse;
  352   	}
  353   
  354   	/**
  355   	 * Determine the connection synchronization order to use for the given
  356   	 * DataSource. Decreased for every level of nesting that a DataSource
  357   	 * has, checked through the level of DelegatingDataSource nesting.
  358   	 * @param dataSource the DataSource to check
  359   	 * @return the connection synchronization order to use
  360   	 * @see #CONNECTION_SYNCHRONIZATION_ORDER
  361   	 */
  362   	private static int getConnectionSynchronizationOrder(DataSource dataSource) {
  363   		int order = CONNECTION_SYNCHRONIZATION_ORDER;
  364   		DataSource currDs = dataSource;
  365   		while (currDs instanceof DelegatingDataSource) {
  366   			order--;
  367   			currDs = ((DelegatingDataSource) currDs).getTargetDataSource();
  368   		}
  369   		return order;
  370   	}
  371   
  372   
  373   	/**
  374   	 * Callback for resource cleanup at the end of a non-native JDBC transaction
  375   	 * (e.g. when participating in a JtaTransactionManager transaction).
  376   	 * @see org.springframework.transaction.jta.JtaTransactionManager
  377   	 */
  378   	private static class ConnectionSynchronization extends TransactionSynchronizationAdapter {
  379   
  380   		private final ConnectionHolder connectionHolder;
  381   
  382   		private final DataSource dataSource;
  383   
  384   		private int order;
  385   
  386   		private boolean holderActive = true;
  387   
  388   		public ConnectionSynchronization(ConnectionHolder connectionHolder, DataSource dataSource) {
  389   			this.connectionHolder = connectionHolder;
  390   			this.dataSource = dataSource;
  391   			this.order = getConnectionSynchronizationOrder(dataSource);
  392   		}
  393   
  394   		public int getOrder() {
  395   			return this.order;
  396   		}
  397   
  398   		public void suspend() {
  399   			if (this.holderActive) {
  400   				TransactionSynchronizationManager.unbindResource(this.dataSource);
  401   				if (this.connectionHolder.hasConnection() && !this.connectionHolder.isOpen()) {
  402   					// Release Connection on suspend if the application doesn't keep
  403   					// a handle to it anymore. We will fetch a fresh Connection if the
  404   					// application accesses the ConnectionHolder again after resume,
  405   					// assuming that it will participate in the same transaction.
  406   					releaseConnection(this.connectionHolder.getConnection(), this.dataSource);
  407   					this.connectionHolder.setConnection(null);
  408   				}
  409   			}
  410   		}
  411   
  412   		public void resume() {
  413   			if (this.holderActive) {
  414   				TransactionSynchronizationManager.bindResource(this.dataSource, this.connectionHolder);
  415   			}
  416   		}
  417   
  418   		public void beforeCompletion() {
  419   			// Release Connection early if the holder is not open anymore
  420   			// (that is, not used by another resource like a Hibernate Session
  421   			// that has its own cleanup via transaction synchronization),
  422   			// to avoid issues with strict JTA implementations that expect
  423   			// the close call before transaction completion.
  424   			if (!this.connectionHolder.isOpen()) {
  425   				TransactionSynchronizationManager.unbindResource(this.dataSource);
  426   				this.holderActive = false;
  427   				if (this.connectionHolder.hasConnection()) {
  428   					releaseConnection(this.connectionHolder.getConnection(), this.dataSource);
  429   				}
  430   			}
  431   		}
  432   
  433   		public void afterCompletion(int status) {
  434   			// If we haven't closed the Connection in beforeCompletion,
  435   			// close it now. The holder might have been used for other
  436   			// cleanup in the meantime, for example by a Hibernate Session.
  437   			if (this.holderActive) {
  438   				// The thread-bound ConnectionHolder might not be available anymore,
  439   				// since afterCompletion might get called from a different thread.
  440   				TransactionSynchronizationManager.unbindResourceIfPossible(this.dataSource);
  441   				this.holderActive = false;
  442   				if (this.connectionHolder.hasConnection()) {
  443   					releaseConnection(this.connectionHolder.getConnection(), this.dataSource);
  444   					// Reset the ConnectionHolder: It might remain bound to the thread.
  445   					this.connectionHolder.setConnection(null);
  446   				}
  447   			}
  448   			this.connectionHolder.reset();
  449   		}
  450   	}
  451   
  452   }

Save This Page
Home » spring-framework-2.5.6-with-dependencies » org.springframework » jdbc » datasource » [javadoc | source]