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