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