001/* 002 * Copyright 2002-2019 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.lang.reflect.InvocationHandler; 020import java.lang.reflect.InvocationTargetException; 021import java.lang.reflect.Method; 022import java.lang.reflect.Proxy; 023import java.sql.Connection; 024import java.sql.SQLException; 025import java.sql.Statement; 026import javax.sql.DataSource; 027 028import org.springframework.transaction.support.TransactionSynchronizationManager; 029import org.springframework.util.Assert; 030 031/** 032 * Proxy for a target JDBC {@link javax.sql.DataSource}, adding awareness of 033 * Spring-managed transactions. Similar to a transactional JNDI DataSource 034 * as provided by a Java EE server. 035 * 036 * <p>Data access code that should remain unaware of Spring's data access support 037 * can work with this proxy to seamlessly participate in Spring-managed transactions. 038 * Note that the transaction manager, for example {@link DataSourceTransactionManager}, 039 * still needs to work with the underlying DataSource, <i>not</i> with this proxy. 040 * 041 * <p><b>Make sure that TransactionAwareDataSourceProxy is the outermost DataSource 042 * of a chain of DataSource proxies/adapters.</b> TransactionAwareDataSourceProxy 043 * can delegate either directly to the target connection pool or to some 044 * intermediary proxy/adapter like {@link LazyConnectionDataSourceProxy} or 045 * {@link UserCredentialsDataSourceAdapter}. 046 * 047 * <p>Delegates to {@link DataSourceUtils} for automatically participating in 048 * thread-bound transactions, for example managed by {@link DataSourceTransactionManager}. 049 * {@code getConnection} calls and {@code close} calls on returned Connections 050 * will behave properly within a transaction, i.e. always operate on the transactional 051 * Connection. If not within a transaction, normal DataSource behavior applies. 052 * 053 * <p>This proxy allows data access code to work with the plain JDBC API and still 054 * participate in Spring-managed transactions, similar to JDBC code in a Java EE/JTA 055 * environment. However, if possible, use Spring's DataSourceUtils, JdbcTemplate or 056 * JDBC operation objects to get transaction participation even without a proxy for 057 * the target DataSource, avoiding the need to define such a proxy in the first place. 058 * 059 * <p>As a further effect, using a transaction-aware DataSource will apply remaining 060 * transaction timeouts to all created JDBC (Prepared/Callable)Statement. This means 061 * that all operations performed through standard JDBC will automatically participate 062 * in Spring-managed transaction timeouts. 063 * 064 * <p><b>NOTE:</b> This DataSource proxy needs to return wrapped Connections 065 * (which implement the {@link ConnectionProxy} interface) in order to handle 066 * close calls properly. Therefore, the returned Connections cannot be cast 067 * to a native JDBC Connection type such as OracleConnection or to a connection 068 * pool implementation type. Use a corresponding 069 * {@link org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor} 070 * or JDBC 4's {@link Connection#unwrap} to retrieve the native JDBC Connection. 071 * 072 * @author Juergen Hoeller 073 * @since 1.1 074 * @see javax.sql.DataSource#getConnection() 075 * @see java.sql.Connection#close() 076 * @see DataSourceUtils#doGetConnection 077 * @see DataSourceUtils#applyTransactionTimeout 078 * @see DataSourceUtils#doReleaseConnection 079 */ 080public class TransactionAwareDataSourceProxy extends DelegatingDataSource { 081 082 private boolean reobtainTransactionalConnections = false; 083 084 085 /** 086 * Create a new TransactionAwareDataSourceProxy. 087 * @see #setTargetDataSource 088 */ 089 public TransactionAwareDataSourceProxy() { 090 } 091 092 /** 093 * Create a new TransactionAwareDataSourceProxy. 094 * @param targetDataSource the target DataSource 095 */ 096 public TransactionAwareDataSourceProxy(DataSource targetDataSource) { 097 super(targetDataSource); 098 } 099 100 /** 101 * Specify whether to reobtain the target Connection for each operation 102 * performed within a transaction. 103 * <p>The default is "false". Specify "true" to reobtain transactional 104 * Connections for every call on the Connection proxy; this is advisable 105 * on JBoss if you hold on to a Connection handle across transaction boundaries. 106 * <p>The effect of this setting is similar to the 107 * "hibernate.connection.release_mode" value "after_statement". 108 */ 109 public void setReobtainTransactionalConnections(boolean reobtainTransactionalConnections) { 110 this.reobtainTransactionalConnections = reobtainTransactionalConnections; 111 } 112 113 114 /** 115 * Delegates to DataSourceUtils for automatically participating in Spring-managed 116 * transactions. Throws the original SQLException, if any. 117 * <p>The returned Connection handle implements the ConnectionProxy interface, 118 * allowing to retrieve the underlying target Connection. 119 * @return a transactional Connection if any, a new one else 120 * @see DataSourceUtils#doGetConnection 121 * @see ConnectionProxy#getTargetConnection 122 */ 123 @Override 124 public Connection getConnection() throws SQLException { 125 DataSource ds = getTargetDataSource(); 126 Assert.state(ds != null, "'targetDataSource' is required"); 127 return getTransactionAwareConnectionProxy(ds); 128 } 129 130 /** 131 * Wraps the given Connection with a proxy that delegates every method call to it 132 * but delegates {@code close()} calls to DataSourceUtils. 133 * @param targetDataSource the DataSource that the Connection came from 134 * @return the wrapped Connection 135 * @see java.sql.Connection#close() 136 * @see DataSourceUtils#doReleaseConnection 137 */ 138 protected Connection getTransactionAwareConnectionProxy(DataSource targetDataSource) { 139 return (Connection) Proxy.newProxyInstance( 140 ConnectionProxy.class.getClassLoader(), 141 new Class<?>[] {ConnectionProxy.class}, 142 new TransactionAwareInvocationHandler(targetDataSource)); 143 } 144 145 /** 146 * Determine whether to obtain a fixed target Connection for the proxy 147 * or to reobtain the target Connection for each operation. 148 * <p>The default implementation returns {@code true} for all 149 * standard cases. This can be overridden through the 150 * {@link #setReobtainTransactionalConnections "reobtainTransactionalConnections"} 151 * flag, which enforces a non-fixed target Connection within an active transaction. 152 * Note that non-transactional access will always use a fixed Connection. 153 * @param targetDataSource the target DataSource 154 */ 155 protected boolean shouldObtainFixedConnection(DataSource targetDataSource) { 156 return (!TransactionSynchronizationManager.isSynchronizationActive() || 157 !this.reobtainTransactionalConnections); 158 } 159 160 161 /** 162 * Invocation handler that delegates close calls on JDBC Connections 163 * to DataSourceUtils for being aware of thread-bound transactions. 164 */ 165 private class TransactionAwareInvocationHandler implements InvocationHandler { 166 167 private final DataSource targetDataSource; 168 169 private Connection target; 170 171 private boolean closed = false; 172 173 public TransactionAwareInvocationHandler(DataSource targetDataSource) { 174 this.targetDataSource = targetDataSource; 175 } 176 177 @Override 178 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 179 // Invocation on ConnectionProxy interface coming in... 180 181 if (method.getName().equals("equals")) { 182 // Only considered as equal when proxies are identical. 183 return (proxy == args[0]); 184 } 185 else if (method.getName().equals("hashCode")) { 186 // Use hashCode of Connection proxy. 187 return System.identityHashCode(proxy); 188 } 189 else if (method.getName().equals("toString")) { 190 // Allow for differentiating between the proxy and the raw Connection. 191 StringBuilder sb = new StringBuilder("Transaction-aware proxy for target Connection "); 192 if (this.target != null) { 193 sb.append("[").append(this.target.toString()).append("]"); 194 } 195 else { 196 sb.append(" from DataSource [").append(this.targetDataSource).append("]"); 197 } 198 return sb.toString(); 199 } 200 else if (method.getName().equals("unwrap")) { 201 if (((Class<?>) args[0]).isInstance(proxy)) { 202 return proxy; 203 } 204 } 205 else if (method.getName().equals("isWrapperFor")) { 206 if (((Class<?>) args[0]).isInstance(proxy)) { 207 return true; 208 } 209 } 210 else if (method.getName().equals("close")) { 211 // Handle close method: only close if not within a transaction. 212 DataSourceUtils.doReleaseConnection(this.target, this.targetDataSource); 213 this.closed = true; 214 return null; 215 } 216 else if (method.getName().equals("isClosed")) { 217 return this.closed; 218 } 219 220 if (this.target == null) { 221 if (method.getName().equals("getWarnings") || method.getName().equals("clearWarnings")) { 222 // Avoid creation of target Connection on pre-close cleanup (e.g. Hibernate Session) 223 return null; 224 } 225 if (this.closed) { 226 throw new SQLException("Connection handle already closed"); 227 } 228 if (shouldObtainFixedConnection(this.targetDataSource)) { 229 this.target = DataSourceUtils.doGetConnection(this.targetDataSource); 230 } 231 } 232 Connection actualTarget = this.target; 233 if (actualTarget == null) { 234 actualTarget = DataSourceUtils.doGetConnection(this.targetDataSource); 235 } 236 237 if (method.getName().equals("getTargetConnection")) { 238 // Handle getTargetConnection method: return underlying Connection. 239 return actualTarget; 240 } 241 242 // Invoke method on target Connection. 243 try { 244 Object retVal = method.invoke(actualTarget, args); 245 246 // If return value is a Statement, apply transaction timeout. 247 // Applies to createStatement, prepareStatement, prepareCall. 248 if (retVal instanceof Statement) { 249 DataSourceUtils.applyTransactionTimeout((Statement) retVal, this.targetDataSource); 250 } 251 252 return retVal; 253 } 254 catch (InvocationTargetException ex) { 255 throw ex.getTargetException(); 256 } 257 finally { 258 if (actualTarget != this.target) { 259 DataSourceUtils.doReleaseConnection(actualTarget, this.targetDataSource); 260 } 261 } 262 } 263 } 264 265}