001/*
002 * Copyright 2002-2012 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.jdbc.datasource;
018
019import java.sql.Connection;
020import java.sql.SQLException;
021
022import org.springframework.core.Constants;
023import org.springframework.transaction.TransactionDefinition;
024import org.springframework.transaction.support.DefaultTransactionDefinition;
025import org.springframework.transaction.support.TransactionSynchronizationManager;
026
027/**
028 * An adapter for a target {@link javax.sql.DataSource}, applying the current
029 * Spring transaction's isolation level (and potentially specified user credentials)
030 * to every {@code getConnection} call. Also applies the read-only flag,
031 * if specified.
032 *
033 * <p>Can be used to proxy a target JNDI DataSource that does not have the
034 * desired isolation level (and user credentials) configured. Client code
035 * can work with this DataSource as usual, not worrying about such settings.
036 *
037 * <p>Inherits the capability to apply specific user credentials from its superclass
038 * {@link UserCredentialsDataSourceAdapter}; see the latter's javadoc for details
039 * on that functionality (e.g. {@link #setCredentialsForCurrentThread}).
040 *
041 * <p><b>WARNING:</b> This adapter simply calls
042 * {@link java.sql.Connection#setTransactionIsolation} and/or
043 * {@link java.sql.Connection#setReadOnly} for every Connection obtained from it.
044 * It does, however, <i>not</i> reset those settings; it rather expects the target
045 * DataSource to perform such resetting as part of its connection pool handling.
046 * <b>Make sure that the target DataSource properly cleans up such transaction state.</b>
047 *
048 * @author Juergen Hoeller
049 * @since 2.0.3
050 * @see #setIsolationLevel
051 * @see #setIsolationLevelName
052 * @see #setUsername
053 * @see #setPassword
054 */
055public class IsolationLevelDataSourceAdapter extends UserCredentialsDataSourceAdapter {
056
057        /** Constants instance for TransactionDefinition */
058        private static final Constants constants = new Constants(TransactionDefinition.class);
059
060        private Integer isolationLevel;
061
062
063        /**
064         * Set the default isolation level by the name of the corresponding constant
065         * in {@link org.springframework.transaction.TransactionDefinition}, e.g.
066         * "ISOLATION_SERIALIZABLE".
067         * <p>If not specified, the target DataSource's default will be used.
068         * Note that a transaction-specific isolation value will always override
069         * any isolation setting specified at the DataSource level.
070         * @param constantName name of the constant
071         * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_UNCOMMITTED
072         * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_COMMITTED
073         * @see org.springframework.transaction.TransactionDefinition#ISOLATION_REPEATABLE_READ
074         * @see org.springframework.transaction.TransactionDefinition#ISOLATION_SERIALIZABLE
075         * @see #setIsolationLevel
076         */
077        public final void setIsolationLevelName(String constantName) throws IllegalArgumentException {
078                if (constantName == null || !constantName.startsWith(DefaultTransactionDefinition.PREFIX_ISOLATION)) {
079                        throw new IllegalArgumentException("Only isolation constants allowed");
080                }
081                setIsolationLevel(constants.asNumber(constantName).intValue());
082        }
083
084        /**
085         * Specify the default isolation level to use for Connection retrieval,
086         * according to the JDBC {@link java.sql.Connection} constants
087         * (equivalent to the corresponding Spring
088         * {@link org.springframework.transaction.TransactionDefinition} constants).
089         * <p>If not specified, the target DataSource's default will be used.
090         * Note that a transaction-specific isolation value will always override
091         * any isolation setting specified at the DataSource level.
092         * @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED
093         * @see java.sql.Connection#TRANSACTION_READ_COMMITTED
094         * @see java.sql.Connection#TRANSACTION_REPEATABLE_READ
095         * @see java.sql.Connection#TRANSACTION_SERIALIZABLE
096         * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_UNCOMMITTED
097         * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_COMMITTED
098         * @see org.springframework.transaction.TransactionDefinition#ISOLATION_REPEATABLE_READ
099         * @see org.springframework.transaction.TransactionDefinition#ISOLATION_SERIALIZABLE
100         * @see org.springframework.transaction.TransactionDefinition#getIsolationLevel()
101         * @see org.springframework.transaction.support.TransactionSynchronizationManager#getCurrentTransactionIsolationLevel()
102         */
103        public void setIsolationLevel(int isolationLevel) {
104                if (!constants.getValues(DefaultTransactionDefinition.PREFIX_ISOLATION).contains(isolationLevel)) {
105                        throw new IllegalArgumentException("Only values of isolation constants allowed");
106                }
107                this.isolationLevel = (isolationLevel != TransactionDefinition.ISOLATION_DEFAULT ? isolationLevel : null);
108        }
109
110        /**
111         * Return the statically specified isolation level,
112         * or {@code null} if none.
113         */
114        protected Integer getIsolationLevel() {
115                return this.isolationLevel;
116        }
117
118
119        /**
120         * Applies the current isolation level value and read-only flag
121         * to the returned Connection.
122         * @see #getCurrentIsolationLevel()
123         * @see #getCurrentReadOnlyFlag()
124         */
125        @Override
126        protected Connection doGetConnection(String username, String password) throws SQLException {
127                Connection con = super.doGetConnection(username, password);
128                Boolean readOnlyToUse = getCurrentReadOnlyFlag();
129                if (readOnlyToUse != null) {
130                        con.setReadOnly(readOnlyToUse);
131                }
132                Integer isolationLevelToUse = getCurrentIsolationLevel();
133                if (isolationLevelToUse != null) {
134                        con.setTransactionIsolation(isolationLevelToUse);
135                }
136                return con;
137        }
138
139        /**
140         * Determine the current isolation level: either the transaction's
141         * isolation level or a statically defined isolation level.
142         * @return the current isolation level, or {@code null} if none
143         * @see org.springframework.transaction.support.TransactionSynchronizationManager#getCurrentTransactionIsolationLevel()
144         * @see #setIsolationLevel
145         */
146        protected Integer getCurrentIsolationLevel() {
147                Integer isolationLevelToUse = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
148                if (isolationLevelToUse == null) {
149                        isolationLevelToUse = getIsolationLevel();
150                }
151                return isolationLevelToUse;
152        }
153
154        /**
155         * Determine the current read-only flag: by default,
156         * the transaction's read-only hint.
157         * @return whether there is a read-only hint for the current scope
158         * @see org.springframework.transaction.support.TransactionSynchronizationManager#isCurrentTransactionReadOnly()
159         */
160        protected Boolean getCurrentReadOnlyFlag() {
161                boolean txReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
162                return (txReadOnly ? Boolean.TRUE : null);
163        }
164
165}