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 * <bean id="dataSourceRouter" class="org.springframework.jdbc.datasource.lookup.IsolationLevelDataSourceRouter"> 047 * <property name="targetDataSources"> 048 * <map> 049 * <entry key="ISOLATION_REPEATABLE_READ" value-ref="myRepeatableReadDataSource"/> 050 * <entry key="ISOLATION_SERIALIZABLE" value-ref="mySerializableDataSource"/> 051 * </map> 052 * </property> 053 * <property name="defaultTargetDataSource" ref="myDefaultDataSource"/> 054 * </bean></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 * <bean id="dataSourceRouter" class="org.springframework.jdbc.datasource.lookup.IsolationLevelDataSourceRouter"> 063 * <property name="targetDataSources"> 064 * <map> 065 * <entry key="ISOLATION_REPEATABLE_READ" value="java:comp/env/jdbc/myrrds"/> 066 * <entry key="ISOLATION_SERIALIZABLE" value="java:comp/env/jdbc/myserds"/> 067 * </map> 068 * </property> 069 * <property name="defaultTargetDataSource" value="java:comp/env/jdbc/mydefds"/> 070 * </bean></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 * <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"> 080 * <property name="allowCustomIsolationLevels" value="true"/> 081 * </bean></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}