All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy Maven / Gradle / Ivy

There is a newer version: 6.2.0
Show newest version
/*
 * Copyright 2002-2023 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.jdbc.datasource;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

import javax.sql.DataSource;

import org.springframework.lang.Nullable;
import org.springframework.transaction.support.TransactionSynchronizationManager;

/**
 * Proxy for a target JDBC {@link javax.sql.DataSource}, adding awareness of
 * Spring-managed transactions. Similar to a transactional JNDI DataSource
 * as provided by a Jakarta EE server.
 *
 * 

Data access code that should remain unaware of Spring's data access support * can work with this proxy to seamlessly participate in Spring-managed transactions. * Note that the transaction manager, for example {@link DataSourceTransactionManager}, * still needs to work with the underlying DataSource, not with this proxy. * *

Make sure that TransactionAwareDataSourceProxy is the outermost DataSource * of a chain of DataSource proxies/adapters. TransactionAwareDataSourceProxy * can delegate either directly to the target connection pool or to some * intermediary proxy/adapter like {@link LazyConnectionDataSourceProxy} or * {@link UserCredentialsDataSourceAdapter}. * *

Delegates to {@link DataSourceUtils} for automatically participating in * thread-bound transactions, for example managed by {@link DataSourceTransactionManager}. * {@code getConnection} calls and {@code close} calls on returned Connections * will behave properly within a transaction, i.e. always operate on the transactional * Connection. If not within a transaction, normal DataSource behavior applies. * *

This proxy allows data access code to work with the plain JDBC API and still * participate in Spring-managed transactions, similar to JDBC code in a Jakarta EE/JTA * environment. However, if possible, use Spring's DataSourceUtils, JdbcTemplate or * JDBC operation objects to get transaction participation even without a proxy for * the target DataSource, avoiding the need to define such a proxy in the first place. * *

As a further effect, using a transaction-aware DataSource will apply remaining * transaction timeouts to all created JDBC (Prepared/Callable)Statement. This means * that all operations performed through standard JDBC will automatically participate * in Spring-managed transaction timeouts. * *

NOTE: This DataSource proxy needs to return wrapped Connections (which * implement the {@link ConnectionProxy} interface) in order to handle close calls * properly. Use {@link Connection#unwrap} to retrieve the native JDBC Connection. * * @author Juergen Hoeller * @since 1.1 * @see javax.sql.DataSource#getConnection() * @see java.sql.Connection#close() * @see DataSourceUtils#doGetConnection * @see DataSourceUtils#applyTransactionTimeout * @see DataSourceUtils#doReleaseConnection */ public class TransactionAwareDataSourceProxy extends DelegatingDataSource { private boolean lazyTransactionalConnections = true; private boolean reobtainTransactionalConnections = false; /** * Create a new TransactionAwareDataSourceProxy. * @see #setTargetDataSource */ public TransactionAwareDataSourceProxy() { } /** * Create a new TransactionAwareDataSourceProxy. * @param targetDataSource the target DataSource */ public TransactionAwareDataSourceProxy(DataSource targetDataSource) { super(targetDataSource); } /** * Specify whether to obtain the transactional target Connection lazily on * actual data access. *

The default is "true". Specify "false" to immediately obtain a target * Connection when a transaction-aware Connection handle is retrieved. * @since 6.1.2 */ public void setLazyTransactionalConnections(boolean lazyTransactionalConnections) { this.lazyTransactionalConnections = lazyTransactionalConnections; } /** * Specify whether to reobtain the target Connection for each operation * performed within a transaction. *

The default is "false". Specify "true" to reobtain transactional * Connections for every call on the Connection proxy; this is advisable * on JBoss if you hold on to a Connection handle across transaction boundaries. *

The effect of this setting is similar to the * "hibernate.connection.release_mode" value "after_statement". */ public void setReobtainTransactionalConnections(boolean reobtainTransactionalConnections) { this.reobtainTransactionalConnections = reobtainTransactionalConnections; } /** * Delegates to DataSourceUtils for automatically participating in Spring-managed * transactions. Throws the original SQLException, if any. *

The returned Connection handle implements the ConnectionProxy interface, * allowing to retrieve the underlying target Connection. * @return a transactional Connection if any, a new one else * @see DataSourceUtils#doGetConnection * @see ConnectionProxy#getTargetConnection */ @Override public Connection getConnection() throws SQLException { DataSource ds = obtainTargetDataSource(); Connection con = getTransactionAwareConnectionProxy(ds); if (!this.lazyTransactionalConnections && shouldObtainFixedConnection(ds)) { ((ConnectionProxy) con).getTargetConnection(); } return con; } /** * Wraps the given Connection with a proxy that delegates every method call to it * but delegates {@code close()} calls to DataSourceUtils. * @param targetDataSource the DataSource that the Connection came from * @return the wrapped Connection * @see java.sql.Connection#close() * @see DataSourceUtils#doReleaseConnection */ protected Connection getTransactionAwareConnectionProxy(DataSource targetDataSource) { return (Connection) Proxy.newProxyInstance( ConnectionProxy.class.getClassLoader(), new Class[] {ConnectionProxy.class}, new TransactionAwareInvocationHandler(targetDataSource)); } /** * Determine whether to obtain a fixed target Connection for the proxy * or to reobtain the target Connection for each operation. *

The default implementation returns {@code true} for all * standard cases. This can be overridden through the * {@link #setReobtainTransactionalConnections "reobtainTransactionalConnections"} * flag, which enforces a non-fixed target Connection within an active transaction. * Note that non-transactional access will always use a fixed Connection. * @param targetDataSource the target DataSource */ protected boolean shouldObtainFixedConnection(DataSource targetDataSource) { return (!TransactionSynchronizationManager.isSynchronizationActive() || !this.reobtainTransactionalConnections); } /** * Invocation handler that delegates close calls on JDBC Connections * to DataSourceUtils for being aware of thread-bound transactions. */ private class TransactionAwareInvocationHandler implements InvocationHandler { private final DataSource targetDataSource; @Nullable private Connection target; private boolean closed = false; public TransactionAwareInvocationHandler(DataSource targetDataSource) { this.targetDataSource = targetDataSource; } @Override @Nullable public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // Invocation on ConnectionProxy interface coming in... switch (method.getName()) { case "equals" -> { // Only considered as equal when proxies are identical. return (proxy == args[0]); } case "hashCode" -> { // Use hashCode of Connection proxy. return System.identityHashCode(proxy); } case "toString" -> { // Allow for differentiating between the proxy and the raw Connection. StringBuilder sb = new StringBuilder("Transaction-aware proxy for target Connection "); if (this.target != null) { sb.append('[').append(this.target).append(']'); } else { sb.append(" from DataSource [").append(this.targetDataSource).append(']'); } return sb.toString(); } case "close" -> { // Handle close method: only close if not within a transaction. DataSourceUtils.doReleaseConnection(this.target, this.targetDataSource); this.closed = true; return null; } case "isClosed" -> { return this.closed; } case "unwrap" -> { if (((Class) args[0]).isInstance(proxy)) { return proxy; } } case "isWrapperFor" -> { if (((Class) args[0]).isInstance(proxy)) { return true; } } } if (this.target == null) { if (method.getName().equals("getWarnings") || method.getName().equals("clearWarnings")) { // Avoid creation of target Connection on pre-close cleanup (e.g. Hibernate Session) return null; } if (this.closed) { throw new SQLException("Connection handle already closed"); } if (shouldObtainFixedConnection(this.targetDataSource)) { this.target = DataSourceUtils.doGetConnection(this.targetDataSource); } } Connection actualTarget = this.target; if (actualTarget == null) { actualTarget = DataSourceUtils.doGetConnection(this.targetDataSource); } if (method.getName().equals("getTargetConnection")) { // Handle getTargetConnection method: return underlying Connection. return actualTarget; } // Invoke method on target Connection. try { Object retVal = method.invoke(actualTarget, args); // If return value is a Statement, apply transaction timeout. // Applies to createStatement, prepareStatement, prepareCall. if (retVal instanceof Statement statement) { DataSourceUtils.applyTransactionTimeout(statement, this.targetDataSource); } return retVal; } catch (InvocationTargetException ex) { throw ex.getTargetException(); } finally { if (actualTarget != this.target) { DataSourceUtils.doReleaseConnection(actualTarget, this.targetDataSource); } } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy