@@ -158,13 +158,73 @@ public static boolean hasResource(Object key) {
158
158
159
159
/**
160
160
* Bind the given resource for the given key to the current thread.
161
+ * <p><b>Note: Any bound resource needs to get explicitly unbound through
162
+ * {@link #unbindResource}. For automatic unbinding after transaction
163
+ * completion, use {@link #bindSynchronizedResource} instead.</b>
161
164
* @param key the key to bind the value to (usually the resource factory)
162
165
* @param value the value to bind (usually the active resource object)
163
166
* @throws IllegalStateException if there is already a value bound to the thread
164
167
* @see ResourceTransactionManager#getResourceFactory()
168
+ * @see #bindSynchronizedResource
165
169
*/
166
170
public static void bindResource (Object key , Object value ) throws IllegalStateException {
167
171
Object actualKey = TransactionSynchronizationUtils .unwrapResourceIfNecessary (key );
172
+ Object oldValue = doBindResource (actualKey , value );
173
+ if (oldValue != null ) {
174
+ throw new IllegalStateException (
175
+ "Already value [" + oldValue + "] for key [" + actualKey + "] bound to thread" );
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Bind the given resource for the given key to the current thread,
181
+ * synchronizing it with the current transaction for automatic unbinding
182
+ * after transaction completion.
183
+ * <p>This is effectively a programmatic way to register a transaction-scoped
184
+ * resource, similar to the BeanFactory-driven {@link SimpleTransactionScope}.
185
+ * <p>An existing value bound for the given key will be preserved and re-bound
186
+ * after transaction completion, restoring the state before this bind call.
187
+ * @param key the key to bind the value to (usually the resource factory)
188
+ * @param value the value to bind (usually the active resource object)
189
+ * @throws IllegalStateException if transaction synchronization is not active
190
+ * @since 7.0
191
+ * @see #bindResource
192
+ * @see #registerSynchronization
193
+ */
194
+ public static void bindSynchronizedResource (Object key , Object value ) throws IllegalStateException {
195
+ Set <TransactionSynchronization > synchs = synchronizations .get ();
196
+ if (synchs == null ) {
197
+ throw new IllegalStateException ("Transaction synchronization is not active" );
198
+ }
199
+ Object actualKey = TransactionSynchronizationUtils .unwrapResourceIfNecessary (key );
200
+ Object oldValue = doBindResource (actualKey , value );
201
+ synchs .add (new TransactionSynchronization () {
202
+ @ Override
203
+ public void suspend () {
204
+ doUnbindResource (actualKey );
205
+ }
206
+ @ Override
207
+ public void resume () {
208
+ Object existingValue = doBindResource (actualKey , value );
209
+ if (existingValue != null ) {
210
+ throw new IllegalStateException (
211
+ "Unexpected value [" + existingValue + "] for key [" + actualKey + "] bound on resume" );
212
+ }
213
+ }
214
+ @ Override
215
+ public void afterCompletion (int status ) {
216
+ doUnbindResource (actualKey );
217
+ if (oldValue != null ) {
218
+ doBindResource (actualKey , oldValue );
219
+ }
220
+ }
221
+ });
222
+ }
223
+
224
+ /**
225
+ * Actually bind the given resource for the given key to the current thread.
226
+ */
227
+ private static @ Nullable Object doBindResource (Object actualKey , Object value ) {
168
228
Assert .notNull (value , "Value must not be null" );
169
229
Map <Object , Object > map = resources .get ();
170
230
// set ThreadLocal Map if none found
@@ -177,18 +237,19 @@ public static void bindResource(Object key, Object value) throws IllegalStateExc
177
237
if (oldValue instanceof ResourceHolder resourceHolder && resourceHolder .isVoid ()) {
178
238
oldValue = null ;
179
239
}
180
- if (oldValue != null ) {
181
- throw new IllegalStateException (
182
- "Already value [" + oldValue + "] for key [" + actualKey + "] bound to thread" );
183
- }
240
+ return oldValue ;
184
241
}
185
242
186
243
/**
187
244
* Unbind a resource for the given key from the current thread.
245
+ * <p>This explicit step is only necessary with {@link #bindResource}.
246
+ * For automatic unbinding, consider {@link #bindSynchronizedResource}.
188
247
* @param key the key to unbind (usually the resource factory)
189
248
* @return the previously bound value (usually the active resource object)
190
249
* @throws IllegalStateException if there is no value bound to the thread
191
250
* @see ResourceTransactionManager#getResourceFactory()
251
+ * @see #bindResource
252
+ * @see #unbindResourceIfPossible
192
253
*/
193
254
public static Object unbindResource (Object key ) throws IllegalStateException {
194
255
Object actualKey = TransactionSynchronizationUtils .unwrapResourceIfNecessary (key );
@@ -201,8 +262,12 @@ public static Object unbindResource(Object key) throws IllegalStateException {
201
262
202
263
/**
203
264
* Unbind a resource for the given key from the current thread.
265
+ * <p>This explicit step is only necessary with {@link #bindResource}.
266
+ * For automatic unbinding, consider {@link #bindSynchronizedResource}.
204
267
* @param key the key to unbind (usually the resource factory)
205
268
* @return the previously bound value, or {@code null} if none bound
269
+ * @see #bindResource
270
+ * @see #unbindResource
206
271
*/
207
272
public static @ Nullable Object unbindResourceIfPossible (Object key ) {
208
273
Object actualKey = TransactionSynchronizationUtils .unwrapResourceIfNecessary (key );
0 commit comments