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