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}