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}