spring DataSourceUtils

来源:互联网 发布:car软件 编辑:程序博客网 时间:2024/06/12 19:25
/* * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *      http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package org.springframework.jdbc.datasource;import java.sql.Connection;import java.sql.SQLException;import java.sql.Statement;import javax.sql.DataSource;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.springframework.jdbc.CannotGetJdbcConnectionException;import org.springframework.transaction.TransactionDefinition;import org.springframework.transaction.support.TransactionSynchronizationAdapter;import org.springframework.transaction.support.TransactionSynchronizationManager;import org.springframework.util.Assert;/** * Helper class that provides static methods for obtaining JDBC Connections from * a {@link javax.sql.DataSource}. Includes special support for Spring-managed * transactional Connections, e.g. managed by {@link DataSourceTransactionManager} * or {@link org.springframework.transaction.jta.JtaTransactionManager}. * * <p>Used internally by Spring's {@link org.springframework.jdbc.core.JdbcTemplate}, * Spring's JDBC operation objects and the JDBC {@link DataSourceTransactionManager}. * Can also be used directly in application code. * * @author Rod Johnson * @author Juergen Hoeller * @see #getConnection * @see #releaseConnection * @see DataSourceTransactionManager * @see org.springframework.transaction.jta.JtaTransactionManager * @see org.springframework.transaction.support.TransactionSynchronizationManager */public abstract class DataSourceUtils {/** * Order value for TransactionSynchronization objects that clean up JDBC Connections. */public static final int CONNECTION_SYNCHRONIZATION_ORDER = 1000;private static final Log logger = LogFactory.getLog(DataSourceUtils.class);/** * Obtain a Connection from the given DataSource. Translates SQLExceptions into * the Spring hierarchy of unchecked generic data access exceptions, simplifying * calling code and making any exception that is thrown more meaningful. * <p>Is aware of a corresponding Connection bound to the current thread, for example * when using {@link DataSourceTransactionManager}. Will bind a Connection to the * thread if transaction synchronization is active, e.g. when running within a * {@link org.springframework.transaction.jta.JtaTransactionManager JTA} transaction). * @param dataSource the DataSource to obtain Connections from * @return a JDBC Connection from the given DataSource * @throws org.springframework.jdbc.CannotGetJdbcConnectionException * if the attempt to get a Connection failed * @see #releaseConnection */public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {try {return doGetConnection(dataSource);}catch (SQLException ex) {throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);}}/** * Actually obtain a JDBC Connection from the given DataSource. * Same as {@link #getConnection}, but throwing the original SQLException. * <p>Is aware of a corresponding Connection bound to the current thread, for example * when using {@link DataSourceTransactionManager}. Will bind a Connection to the thread * if transaction synchronization is active (e.g. if in a JTA transaction). * <p>Directly accessed by {@link TransactionAwareDataSourceProxy}. * @param dataSource the DataSource to obtain Connections from * @return a JDBC Connection from the given DataSource * @throws SQLException if thrown by JDBC methods * @see #doReleaseConnection */public static Connection doGetConnection(DataSource dataSource) throws SQLException {Assert.notNull(dataSource, "No DataSource specified");ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {conHolder.requested();if (!conHolder.hasConnection()) {logger.debug("Fetching resumed JDBC Connection from DataSource");conHolder.setConnection(dataSource.getConnection());}Connection connection = conHolder.getConnection();System.out.println("threadId==="+Thread.currentThread().getId()+" connectionHashCode= "+connection.hashCode());return connection;}// Else we either got no holder or an empty thread-bound holder here.logger.debug("Fetching JDBC Connection from DataSource");Connection con = dataSource.getConnection();if (TransactionSynchronizationManager.isSynchronizationActive()) {logger.debug("Registering transaction synchronization for JDBC Connection");// Use same Connection for further JDBC actions within the transaction.// Thread-bound object will get removed by synchronization at transaction completion.ConnectionHolder holderToUse = conHolder;if (holderToUse == null) {holderToUse = new ConnectionHolder(con);}else {holderToUse.setConnection(con);}holderToUse.requested();TransactionSynchronizationManager.registerSynchronization(new ConnectionSynchronization(holderToUse, dataSource));holderToUse.setSynchronizedWithTransaction(true);if (holderToUse != conHolder) {TransactionSynchronizationManager.bindResource(dataSource, holderToUse);}}return con;}/** * Prepare the given Connection with the given transaction semantics. * @param con the Connection to prepare * @param definition the transaction definition to apply * @return the previous isolation level, if any * @throws SQLException if thrown by JDBC methods * @see #resetConnectionAfterTransaction */public static Integer prepareConnectionForTransaction(Connection con, TransactionDefinition definition)throws SQLException {Assert.notNull(con, "No Connection specified");// Set read-only flag.if (definition != null && definition.isReadOnly()) {try {if (logger.isDebugEnabled()) {logger.debug("Setting JDBC Connection [" + con + "] read-only");}con.setReadOnly(true);}catch (SQLException ex) {Throwable exToCheck = ex;while (exToCheck != null) {if (exToCheck.getClass().getSimpleName().contains("Timeout")) {// Assume it's a connection timeout that would otherwise get lost: e.g. from JDBC 4.0throw ex;}exToCheck = exToCheck.getCause();}// "read-only not supported" SQLException -> ignore, it's just a hint anywaylogger.debug("Could not set JDBC Connection read-only", ex);}catch (RuntimeException ex) {Throwable exToCheck = ex;while (exToCheck != null) {if (exToCheck.getClass().getSimpleName().contains("Timeout")) {// Assume it's a connection timeout that would otherwise get lost: e.g. from Hibernatethrow ex;}exToCheck = exToCheck.getCause();}// "read-only not supported" UnsupportedOperationException -> ignore, it's just a hint anywaylogger.debug("Could not set JDBC Connection read-only", ex);}}// Apply specific isolation level, if any.Integer previousIsolationLevel = null;if (definition != null && definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {if (logger.isDebugEnabled()) {logger.debug("Changing isolation level of JDBC Connection [" + con + "] to " +definition.getIsolationLevel());}int currentIsolation = con.getTransactionIsolation();if (currentIsolation != definition.getIsolationLevel()) {previousIsolationLevel = currentIsolation;con.setTransactionIsolation(definition.getIsolationLevel());}}return previousIsolationLevel;}/** * Reset the given Connection after a transaction, * regarding read-only flag and isolation level. * @param con the Connection to reset * @param previousIsolationLevel the isolation level to restore, if any * @see #prepareConnectionForTransaction */public static void resetConnectionAfterTransaction(Connection con, Integer previousIsolationLevel) {Assert.notNull(con, "No Connection specified");try {// Reset transaction isolation to previous value, if changed for the transaction.if (previousIsolationLevel != null) {if (logger.isDebugEnabled()) {logger.debug("Resetting isolation level of JDBC Connection [" +con + "] to " + previousIsolationLevel);}con.setTransactionIsolation(previousIsolationLevel);}// Reset read-only flag.if (con.isReadOnly()) {if (logger.isDebugEnabled()) {logger.debug("Resetting read-only flag of JDBC Connection [" + con + "]");}con.setReadOnly(false);}}catch (Throwable ex) {logger.debug("Could not reset JDBC Connection after transaction", ex);}}/** * Determine whether the given JDBC Connection is transactional, that is, * bound to the current thread by Spring's transaction facilities. * @param con the Connection to check * @param dataSource the DataSource that the Connection was obtained from * (may be {@code null}) * @return whether the Connection is transactional */public static boolean isConnectionTransactional(Connection con, DataSource dataSource) {if (dataSource == null) {return false;}ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);return (conHolder != null && connectionEquals(conHolder, con));}/** * Apply the current transaction timeout, if any, * to the given JDBC Statement object. * @param stmt the JDBC Statement object * @param dataSource the DataSource that the Connection was obtained from * @throws SQLException if thrown by JDBC methods * @see java.sql.Statement#setQueryTimeout */public static void applyTransactionTimeout(Statement stmt, DataSource dataSource) throws SQLException {applyTimeout(stmt, dataSource, 0);}/** * Apply the specified timeout - overridden by the current transaction timeout, * if any - to the given JDBC Statement object. * @param stmt the JDBC Statement object * @param dataSource the DataSource that the Connection was obtained from * @param timeout the timeout to apply (or 0 for no timeout outside of a transaction) * @throws SQLException if thrown by JDBC methods * @see java.sql.Statement#setQueryTimeout */public static void applyTimeout(Statement stmt, DataSource dataSource, int timeout) throws SQLException {Assert.notNull(stmt, "No Statement specified");Assert.notNull(dataSource, "No DataSource specified");ConnectionHolder holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);if (holder != null && holder.hasTimeout()) {// Remaining transaction timeout overrides specified value.stmt.setQueryTimeout(holder.getTimeToLiveInSeconds());}else if (timeout > 0) {// No current transaction timeout -> apply specified value.stmt.setQueryTimeout(timeout);}}/** * Close the given Connection, obtained from the given DataSource, * if it is not managed externally (that is, not bound to the thread). * @param con the Connection to close if necessary * (if this is {@code null}, the call will be ignored) * @param dataSource the DataSource that the Connection was obtained from * (may be {@code null}) * @see #getConnection */public static void releaseConnection(Connection con, DataSource dataSource) {try {doReleaseConnection(con, dataSource);}catch (SQLException ex) {logger.debug("Could not close JDBC Connection", ex);}catch (Throwable ex) {logger.debug("Unexpected exception on closing JDBC Connection", ex);}}/** * Actually close the given Connection, obtained from the given DataSource. * Same as {@link #releaseConnection}, but throwing the original SQLException. * <p>Directly accessed by {@link TransactionAwareDataSourceProxy}. * @param con the Connection to close if necessary * (if this is {@code null}, the call will be ignored) * @param dataSource the DataSource that the Connection was obtained from * (may be {@code null}) * @throws SQLException if thrown by JDBC methods * @see #doGetConnection */public static void doReleaseConnection(Connection con, DataSource dataSource) throws SQLException {if (con == null) {return;}if (dataSource != null) {ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);if (conHolder != null && connectionEquals(conHolder, con)) {// It's the transactional Connection: Don't close it.conHolder.released();return;}}logger.debug("Returning JDBC Connection to DataSource");doCloseConnection(con, dataSource);}/** * Close the Connection, unless a {@link SmartDataSource} doesn't want us to. * @param con the Connection to close if necessary * @param dataSource the DataSource that the Connection was obtained from * @throws SQLException if thrown by JDBC methods * @see Connection#close() * @see SmartDataSource#shouldClose(Connection) */public static void doCloseConnection(Connection con, DataSource dataSource) throws SQLException {if (!(dataSource instanceof SmartDataSource) || ((SmartDataSource) dataSource).shouldClose(con)) {con.close();}}/** * Determine whether the given two Connections are equal, asking the target * Connection in case of a proxy. Used to detect equality even if the * user passed in a raw target Connection while the held one is a proxy. * @param conHolder the ConnectionHolder for the held Connection (potentially a proxy) * @param passedInCon the Connection passed-in by the user * (potentially a target Connection without proxy) * @return whether the given Connections are equal * @see #getTargetConnection */private static boolean connectionEquals(ConnectionHolder conHolder, Connection passedInCon) {if (!conHolder.hasConnection()) {return false;}Connection heldCon = conHolder.getConnection();// Explicitly check for identity too: for Connection handles that do not implement// "equals" properly, such as the ones Commons DBCP exposes).return (heldCon == passedInCon || heldCon.equals(passedInCon) ||getTargetConnection(heldCon).equals(passedInCon));}/** * Return the innermost target Connection of the given Connection. If the given * Connection is a proxy, it will be unwrapped until a non-proxy Connection is * found. Otherwise, the passed-in Connection will be returned as-is. * @param con the Connection proxy to unwrap * @return the innermost target Connection, or the passed-in one if no proxy * @see ConnectionProxy#getTargetConnection() */public static Connection getTargetConnection(Connection con) {Connection conToUse = con;while (conToUse instanceof ConnectionProxy) {conToUse = ((ConnectionProxy) conToUse).getTargetConnection();}return conToUse;}/** * Determine the connection synchronization order to use for the given * DataSource. Decreased for every level of nesting that a DataSource * has, checked through the level of DelegatingDataSource nesting. * @param dataSource the DataSource to check * @return the connection synchronization order to use * @see #CONNECTION_SYNCHRONIZATION_ORDER */private static int getConnectionSynchronizationOrder(DataSource dataSource) {int order = CONNECTION_SYNCHRONIZATION_ORDER;DataSource currDs = dataSource;while (currDs instanceof DelegatingDataSource) {order--;currDs = ((DelegatingDataSource) currDs).getTargetDataSource();}return order;}/** * Callback for resource cleanup at the end of a non-native JDBC transaction * (e.g. when participating in a JtaTransactionManager transaction). * @see org.springframework.transaction.jta.JtaTransactionManager */private static class ConnectionSynchronization extends TransactionSynchronizationAdapter {private final ConnectionHolder connectionHolder;private final DataSource dataSource;private int order;private boolean holderActive = true;public ConnectionSynchronization(ConnectionHolder connectionHolder, DataSource dataSource) {this.connectionHolder = connectionHolder;this.dataSource = dataSource;this.order = getConnectionSynchronizationOrder(dataSource);}@Overridepublic int getOrder() {return this.order;}@Overridepublic void suspend() {if (this.holderActive) {TransactionSynchronizationManager.unbindResource(this.dataSource);if (this.connectionHolder.hasConnection() && !this.connectionHolder.isOpen()) {// Release Connection on suspend if the application doesn't keep// a handle to it anymore. We will fetch a fresh Connection if the// application accesses the ConnectionHolder again after resume,// assuming that it will participate in the same transaction.releaseConnection(this.connectionHolder.getConnection(), this.dataSource);this.connectionHolder.setConnection(null);}}}@Overridepublic void resume() {if (this.holderActive) {TransactionSynchronizationManager.bindResource(this.dataSource, this.connectionHolder);}}@Overridepublic void beforeCompletion() {// Release Connection early if the holder is not open anymore// (that is, not used by another resource like a Hibernate Session// that has its own cleanup via transaction synchronization),// to avoid issues with strict JTA implementations that expect// the close call before transaction completion.if (!this.connectionHolder.isOpen()) {TransactionSynchronizationManager.unbindResource(this.dataSource);this.holderActive = false;if (this.connectionHolder.hasConnection()) {releaseConnection(this.connectionHolder.getConnection(), this.dataSource);}}}@Overridepublic void afterCompletion(int status) {// If we haven't closed the Connection in beforeCompletion,// close it now. The holder might have been used for other// cleanup in the meantime, for example by a Hibernate Session.if (this.holderActive) {// The thread-bound ConnectionHolder might not be available anymore,// since afterCompletion might get called from a different thread.TransactionSynchronizationManager.unbindResourceIfPossible(this.dataSource);this.holderActive = false;if (this.connectionHolder.hasConnection()) {releaseConnection(this.connectionHolder.getConnection(), this.dataSource);// Reset the ConnectionHolder: It might remain bound to the thread.this.connectionHolder.setConnection(null);}}this.connectionHolder.reset();}}}

 

0 0
原创粉丝点击