/* * Copyright 2002-2007 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 org.springframework.core.Constants; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.support.DefaultTransactionDefinition; import org.springframework.transaction.support.TransactionSynchronizationManager; /** * An adapter for a target {@link javax.sql.DataSource}, applying the current * Spring transaction's isolation level (and potentially specified user credentials) * to every <code>getConnection</code> call. Also applies the read-only flag, * if specified. * * <p>Can be used to proxy a target JNDI DataSource that does not have the * desired isolation level (and user credentials) configured. Client code * can work with this DataSource as usual, not worrying about such settings. * * <p>Inherits the capability to apply specific user credentials from its superclass * {@link UserCredentialsDataSourceAdapter}; see the latter's javadoc for details * on that functionality (e.g. {@link #setCredentialsForCurrentThread}). * * <p><b>WARNING:</b> This adapter simply calls * {@link java.sql.Connection#setTransactionIsolation} and/or * {@link java.sql.Connection#setReadOnly} for every Connection obtained from it. * It does, however, <i>not</i> reset those settings; it rather expects the target * DataSource to perform such resetting as part of its connection pool handling. * <b>Make sure that the target DataSource properly cleans up such transaction state.</b> * * @author Juergen Hoeller * @since 2.0.3 * @see #setIsolationLevel * @see #setIsolationLevelName * @see #setUsername * @see #setPassword */ public class IsolationLevelDataSourceAdapter extends UserCredentialsDataSourceAdapter { /** Constants instance for TransactionDefinition */ private static final Constants constants = new Constants(TransactionDefinition.class); private Integer isolationLevel; /** * Set the default isolation level by the name of the corresponding constant * in {@link org.springframework.transaction.TransactionDefinition}, e.g. * "ISOLATION_SERIALIZABLE". * <p>If not specified, the target DataSource's default will be used. * Note that a transaction-specific isolation value will always override * any isolation setting specified at the DataSource level. * @param constantName name of the constant * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_UNCOMMITTED * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_COMMITTED * @see org.springframework.transaction.TransactionDefinition#ISOLATION_REPEATABLE_READ * @see org.springframework.transaction.TransactionDefinition#ISOLATION_SERIALIZABLE * @see #setIsolationLevel */ public final void setIsolationLevelName(String constantName) throws IllegalArgumentException { if (constantName == null || !constantName.startsWith(DefaultTransactionDefinition.PREFIX_ISOLATION)) { throw new IllegalArgumentException("Only isolation constants allowed"); } setIsolationLevel(constants.asNumber(constantName).intValue()); } /** * Specify the default isolation level to use for Connection retrieval, * according to the JDBC {@link java.sql.Connection} constants * (equivalent to the corresponding Spring * {@link org.springframework.transaction.TransactionDefinition} constants). * <p>If not specified, the target DataSource's default will be used. * Note that a transaction-specific isolation value will always override * any isolation setting specified at the DataSource level. * @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED * @see java.sql.Connection#TRANSACTION_READ_COMMITTED * @see java.sql.Connection#TRANSACTION_REPEATABLE_READ * @see java.sql.Connection#TRANSACTION_SERIALIZABLE * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_UNCOMMITTED * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_COMMITTED * @see org.springframework.transaction.TransactionDefinition#ISOLATION_REPEATABLE_READ * @see org.springframework.transaction.TransactionDefinition#ISOLATION_SERIALIZABLE * @see org.springframework.transaction.TransactionDefinition#getIsolationLevel() * @see org.springframework.transaction.support.TransactionSynchronizationManager#getCurrentTransactionIsolationLevel() */ public void setIsolationLevel(int isolationLevel) { if (!constants.getValues(DefaultTransactionDefinition.PREFIX_ISOLATION).contains(new Integer(isolationLevel))) { throw new IllegalArgumentException("Only values of isolation constants allowed"); } this.isolationLevel = (isolationLevel != TransactionDefinition.ISOLATION_DEFAULT ? new Integer(isolationLevel) : null); } /** * Return the statically specified isolation level, * or <code>null</code> if none. */ protected Integer getIsolationLevel() { return this.isolationLevel; } /** * Applies the current isolation level value and read-only flag * to the returned Connection. * @see #getCurrentIsolationLevel() * @see #getCurrentReadOnlyFlag() */ protected Connection doGetConnection(String username, String password) throws SQLException { Connection con = super.doGetConnection(username, password); Boolean readOnlyToUse = getCurrentReadOnlyFlag(); if (readOnlyToUse != null) { con.setReadOnly(readOnlyToUse.booleanValue()); } Integer isolationLevelToUse = getCurrentIsolationLevel(); if (isolationLevelToUse != null) { con.setTransactionIsolation(isolationLevelToUse.intValue()); } return con; } /** * Determine the current isolation level: either the transaction's * isolation level or a statically defined isolation level. * @return the current isolation level, or <code>null</code> if none * @see org.springframework.transaction.support.TransactionSynchronizationManager#getCurrentTransactionIsolationLevel() * @see #setIsolationLevel */ protected Integer getCurrentIsolationLevel() { Integer isolationLevelToUse = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel(); if (isolationLevelToUse == null) { isolationLevelToUse = getIsolationLevel(); } return isolationLevelToUse; } /** * Determine the current read-only flag: by default, * the transaction's read-only hint. * @return whether there is a read-only hint for the current scope * @see org.springframework.transaction.support.TransactionSynchronizationManager#isCurrentTransactionReadOnly() */ protected Boolean getCurrentReadOnlyFlag() { boolean txReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly(); return (txReadOnly ? Boolean.TRUE : null); } }