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.lookup;
018
019import org.springframework.core.Constants;
020import org.springframework.lang.Nullable;
021import org.springframework.transaction.TransactionDefinition;
022import org.springframework.transaction.support.DefaultTransactionDefinition;
023import org.springframework.transaction.support.TransactionSynchronizationManager;
024
025/**
026 * DataSource that routes to one of various target DataSources based on the
027 * current transaction isolation level. The target DataSources need to be
028 * configured with the isolation level name as key, as defined on the
029 * {@link org.springframework.transaction.TransactionDefinition TransactionDefinition interface}.
030 *
031 * <p>This is particularly useful in combination with JTA transaction management
032 * (typically through Spring's {@link org.springframework.transaction.jta.JtaTransactionManager}).
033 * Standard JTA does not support transaction-specific isolation levels. Some JTA
034 * providers support isolation levels as a vendor-specific extension (e.g. WebLogic),
035 * which is the preferred way of addressing this. As alternative (e.g. on WebSphere),
036 * the target database can be represented through multiple JNDI DataSources, each
037 * configured with a different isolation level (for the entire DataSource).
038 * The present DataSource router allows to transparently switch to the
039 * appropriate DataSource based on the current transaction's isolation level.
040 *
041 * <p>The configuration can for example look like this, assuming that the target
042 * DataSources are defined as individual Spring beans with names
043 * "myRepeatableReadDataSource", "mySerializableDataSource" and "myDefaultDataSource":
044 *
045 * <pre class="code">
046 * &lt;bean id="dataSourceRouter" class="org.springframework.jdbc.datasource.lookup.IsolationLevelDataSourceRouter"&gt;
047 *   &lt;property name="targetDataSources"&gt;
048 *     &lt;map&gt;
049 *       &lt;entry key="ISOLATION_REPEATABLE_READ" value-ref="myRepeatableReadDataSource"/&gt;
050 *       &lt;entry key="ISOLATION_SERIALIZABLE" value-ref="mySerializableDataSource"/&gt;
051 *     &lt;/map&gt;
052 *   &lt;/property&gt;
053 *   &lt;property name="defaultTargetDataSource" ref="myDefaultDataSource"/&gt;
054 * &lt;/bean&gt;</pre>
055 *
056 * Alternatively, the keyed values can also be data source names, to be resolved
057 * through a {@link #setDataSourceLookup DataSourceLookup}: by default, JNDI
058 * names for a standard JNDI lookup. This allows for a single concise definition
059 * without the need for separate DataSource bean definitions.
060 *
061 * <pre class="code">
062 * &lt;bean id="dataSourceRouter" class="org.springframework.jdbc.datasource.lookup.IsolationLevelDataSourceRouter"&gt;
063 *   &lt;property name="targetDataSources"&gt;
064 *     &lt;map&gt;
065 *       &lt;entry key="ISOLATION_REPEATABLE_READ" value="java:comp/env/jdbc/myrrds"/&gt;
066 *       &lt;entry key="ISOLATION_SERIALIZABLE" value="java:comp/env/jdbc/myserds"/&gt;
067 *     &lt;/map&gt;
068 *   &lt;/property&gt;
069 *   &lt;property name="defaultTargetDataSource" value="java:comp/env/jdbc/mydefds"/&gt;
070 * &lt;/bean&gt;</pre>
071 *
072 * Note: If you are using this router in combination with Spring's
073 * {@link org.springframework.transaction.jta.JtaTransactionManager},
074 * don't forget to switch the "allowCustomIsolationLevels" flag to "true".
075 * (By default, JtaTransactionManager will only accept a default isolation level
076 * because of the lack of isolation level support in standard JTA itself.)
077 *
078 * <pre class="code">
079 * &lt;bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"&gt;
080 *   &lt;property name="allowCustomIsolationLevels" value="true"/&gt;
081 * &lt;/bean&gt;</pre>
082 *
083 * @author Juergen Hoeller
084 * @since 2.0.1
085 * @see #setTargetDataSources
086 * @see #setDefaultTargetDataSource
087 * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_UNCOMMITTED
088 * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_COMMITTED
089 * @see org.springframework.transaction.TransactionDefinition#ISOLATION_REPEATABLE_READ
090 * @see org.springframework.transaction.TransactionDefinition#ISOLATION_SERIALIZABLE
091 * @see org.springframework.transaction.jta.JtaTransactionManager
092 */
093public class IsolationLevelDataSourceRouter extends AbstractRoutingDataSource {
094
095        /** Constants instance for TransactionDefinition. */
096        private static final Constants constants = new Constants(TransactionDefinition.class);
097
098
099        /**
100         * Supports Integer values for the isolation level constants
101         * as well as isolation level names as defined on the
102         * {@link org.springframework.transaction.TransactionDefinition TransactionDefinition interface}.
103         */
104        @Override
105        protected Object resolveSpecifiedLookupKey(Object lookupKey) {
106                if (lookupKey instanceof Integer) {
107                        return lookupKey;
108                }
109                else if (lookupKey instanceof String) {
110                        String constantName = (String) lookupKey;
111                        if (!constantName.startsWith(DefaultTransactionDefinition.PREFIX_ISOLATION)) {
112                                throw new IllegalArgumentException("Only isolation constants allowed");
113                        }
114                        return constants.asNumber(constantName);
115                }
116                else {
117                        throw new IllegalArgumentException(
118                                        "Invalid lookup key - needs to be isolation level Integer or isolation level name String: " + lookupKey);
119                }
120        }
121
122        @Override
123        @Nullable
124        protected Object determineCurrentLookupKey() {
125                return TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
126        }
127
128}