diff --git a/spring-tx/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java b/spring-tx/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java
index 06c6150978..51b861a8c2 100644
--- a/spring-tx/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java
+++ b/spring-tx/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java
@@ -158,13 +158,73 @@ public abstract class TransactionSynchronizationManager {
/**
* Bind the given resource for the given key to the current thread.
+ *
Note: Any bound resource needs to get explicitly unbound through
+ * {@link #unbindResource}. For automatic unbinding after transaction
+ * completion, use {@link #bindSynchronizedResource} instead.
* @param key the key to bind the value to (usually the resource factory)
* @param value the value to bind (usually the active resource object)
* @throws IllegalStateException if there is already a value bound to the thread
* @see ResourceTransactionManager#getResourceFactory()
+ * @see #bindSynchronizedResource
*/
public static void bindResource(Object key, Object value) throws IllegalStateException {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
+ Object oldValue = doBindResource(actualKey, value);
+ if (oldValue != null) {
+ throw new IllegalStateException(
+ "Already value [" + oldValue + "] for key [" + actualKey + "] bound to thread");
+ }
+ }
+
+ /**
+ * Bind the given resource for the given key to the current thread,
+ * synchronizing it with the current transaction for automatic unbinding
+ * after transaction completion.
+ *
This is effectively a programmatic way to register a transaction-scoped
+ * resource, similar to the BeanFactory-driven {@link SimpleTransactionScope}.
+ *
An existing value bound for the given key will be preserved and re-bound
+ * after transaction completion, restoring the state before this bind call.
+ * @param key the key to bind the value to (usually the resource factory)
+ * @param value the value to bind (usually the active resource object)
+ * @throws IllegalStateException if transaction synchronization is not active
+ * @since 7.0
+ * @see #bindResource
+ * @see #registerSynchronization
+ */
+ public static void bindSynchronizedResource(Object key, Object value) throws IllegalStateException {
+ Set synchs = synchronizations.get();
+ if (synchs == null) {
+ throw new IllegalStateException("Transaction synchronization is not active");
+ }
+ Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
+ Object oldValue = doBindResource(actualKey, value);
+ synchs.add(new TransactionSynchronization() {
+ @Override
+ public void suspend() {
+ doUnbindResource(actualKey);
+ }
+ @Override
+ public void resume() {
+ Object existingValue = doBindResource(actualKey, value);
+ if (existingValue != null) {
+ throw new IllegalStateException(
+ "Unexpected value [" + existingValue + "] for key [" + actualKey + "] bound on resume");
+ }
+ }
+ @Override
+ public void afterCompletion(int status) {
+ doUnbindResource(actualKey);
+ if (oldValue != null) {
+ doBindResource(actualKey, oldValue);
+ }
+ }
+ });
+ }
+
+ /**
+ * Actually bind the given resource for the given key to the current thread.
+ */
+ private static @Nullable Object doBindResource(Object actualKey, Object value) {
Assert.notNull(value, "Value must not be null");
Map