001/* 002 * Copyright 2002-2020 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.transaction.support; 018 019import java.util.ArrayList; 020import java.util.Collections; 021import java.util.HashMap; 022import java.util.LinkedHashSet; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026 027import org.apache.commons.logging.Log; 028import org.apache.commons.logging.LogFactory; 029 030import org.springframework.core.NamedThreadLocal; 031import org.springframework.core.annotation.AnnotationAwareOrderComparator; 032import org.springframework.lang.Nullable; 033import org.springframework.util.Assert; 034 035/** 036 * Central delegate that manages resources and transaction synchronizations per thread. 037 * To be used by resource management code but not by typical application code. 038 * 039 * <p>Supports one resource per key without overwriting, that is, a resource needs 040 * to be removed before a new one can be set for the same key. 041 * Supports a list of transaction synchronizations if synchronization is active. 042 * 043 * <p>Resource management code should check for thread-bound resources, e.g. JDBC 044 * Connections or Hibernate Sessions, via {@code getResource}. Such code is 045 * normally not supposed to bind resources to threads, as this is the responsibility 046 * of transaction managers. A further option is to lazily bind on first use if 047 * transaction synchronization is active, for performing transactions that span 048 * an arbitrary number of resources. 049 * 050 * <p>Transaction synchronization must be activated and deactivated by a transaction 051 * manager via {@link #initSynchronization()} and {@link #clearSynchronization()}. 052 * This is automatically supported by {@link AbstractPlatformTransactionManager}, 053 * and thus by all standard Spring transaction managers, such as 054 * {@link org.springframework.transaction.jta.JtaTransactionManager} and 055 * {@link org.springframework.jdbc.datasource.DataSourceTransactionManager}. 056 * 057 * <p>Resource management code should only register synchronizations when this 058 * manager is active, which can be checked via {@link #isSynchronizationActive}; 059 * it should perform immediate resource cleanup else. If transaction synchronization 060 * isn't active, there is either no current transaction, or the transaction manager 061 * doesn't support transaction synchronization. 062 * 063 * <p>Synchronization is for example used to always return the same resources 064 * within a JTA transaction, e.g. a JDBC Connection or a Hibernate Session for 065 * any given DataSource or SessionFactory, respectively. 066 * 067 * @author Juergen Hoeller 068 * @since 02.06.2003 069 * @see #isSynchronizationActive 070 * @see #registerSynchronization 071 * @see TransactionSynchronization 072 * @see AbstractPlatformTransactionManager#setTransactionSynchronization 073 * @see org.springframework.transaction.jta.JtaTransactionManager 074 * @see org.springframework.jdbc.datasource.DataSourceTransactionManager 075 * @see org.springframework.jdbc.datasource.DataSourceUtils#getConnection 076 */ 077public abstract class TransactionSynchronizationManager { 078 079 private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class); 080 081 private static final ThreadLocal<Map<Object, Object>> resources = 082 new NamedThreadLocal<>("Transactional resources"); 083 084 private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = 085 new NamedThreadLocal<>("Transaction synchronizations"); 086 087 private static final ThreadLocal<String> currentTransactionName = 088 new NamedThreadLocal<>("Current transaction name"); 089 090 private static final ThreadLocal<Boolean> currentTransactionReadOnly = 091 new NamedThreadLocal<>("Current transaction read-only status"); 092 093 private static final ThreadLocal<Integer> currentTransactionIsolationLevel = 094 new NamedThreadLocal<>("Current transaction isolation level"); 095 096 private static final ThreadLocal<Boolean> actualTransactionActive = 097 new NamedThreadLocal<>("Actual transaction active"); 098 099 100 //------------------------------------------------------------------------- 101 // Management of transaction-associated resource handles 102 //------------------------------------------------------------------------- 103 104 /** 105 * Return all resources that are bound to the current thread. 106 * <p>Mainly for debugging purposes. Resource managers should always invoke 107 * {@code hasResource} for a specific resource key that they are interested in. 108 * @return a Map with resource keys (usually the resource factory) and resource 109 * values (usually the active resource object), or an empty Map if there are 110 * currently no resources bound 111 * @see #hasResource 112 */ 113 public static Map<Object, Object> getResourceMap() { 114 Map<Object, Object> map = resources.get(); 115 return (map != null ? Collections.unmodifiableMap(map) : Collections.emptyMap()); 116 } 117 118 /** 119 * Check if there is a resource for the given key bound to the current thread. 120 * @param key the key to check (usually the resource factory) 121 * @return if there is a value bound to the current thread 122 * @see ResourceTransactionManager#getResourceFactory() 123 */ 124 public static boolean hasResource(Object key) { 125 Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key); 126 Object value = doGetResource(actualKey); 127 return (value != null); 128 } 129 130 /** 131 * Retrieve a resource for the given key that is bound to the current thread. 132 * @param key the key to check (usually the resource factory) 133 * @return a value bound to the current thread (usually the active 134 * resource object), or {@code null} if none 135 * @see ResourceTransactionManager#getResourceFactory() 136 */ 137 @Nullable 138 public static Object getResource(Object key) { 139 Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key); 140 Object value = doGetResource(actualKey); 141 if (value != null && logger.isTraceEnabled()) { 142 logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" + 143 Thread.currentThread().getName() + "]"); 144 } 145 return value; 146 } 147 148 /** 149 * Actually check the value of the resource that is bound for the given key. 150 */ 151 @Nullable 152 private static Object doGetResource(Object actualKey) { 153 Map<Object, Object> map = resources.get(); 154 if (map == null) { 155 return null; 156 } 157 Object value = map.get(actualKey); 158 // Transparently remove ResourceHolder that was marked as void... 159 if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) { 160 map.remove(actualKey); 161 // Remove entire ThreadLocal if empty... 162 if (map.isEmpty()) { 163 resources.remove(); 164 } 165 value = null; 166 } 167 return value; 168 } 169 170 /** 171 * Bind the given resource for the given key to the current thread. 172 * @param key the key to bind the value to (usually the resource factory) 173 * @param value the value to bind (usually the active resource object) 174 * @throws IllegalStateException if there is already a value bound to the thread 175 * @see ResourceTransactionManager#getResourceFactory() 176 */ 177 public static void bindResource(Object key, Object value) throws IllegalStateException { 178 Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key); 179 Assert.notNull(value, "Value must not be null"); 180 Map<Object, Object> map = resources.get(); 181 // set ThreadLocal Map if none found 182 if (map == null) { 183 map = new HashMap<>(); 184 resources.set(map); 185 } 186 Object oldValue = map.put(actualKey, value); 187 // Transparently suppress a ResourceHolder that was marked as void... 188 if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) { 189 oldValue = null; 190 } 191 if (oldValue != null) { 192 throw new IllegalStateException("Already value [" + oldValue + "] for key [" + 193 actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]"); 194 } 195 if (logger.isTraceEnabled()) { 196 logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" + 197 Thread.currentThread().getName() + "]"); 198 } 199 } 200 201 /** 202 * Unbind a resource for the given key from the current thread. 203 * @param key the key to unbind (usually the resource factory) 204 * @return the previously bound value (usually the active resource object) 205 * @throws IllegalStateException if there is no value bound to the thread 206 * @see ResourceTransactionManager#getResourceFactory() 207 */ 208 public static Object unbindResource(Object key) throws IllegalStateException { 209 Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key); 210 Object value = doUnbindResource(actualKey); 211 if (value == null) { 212 throw new IllegalStateException( 213 "No value for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]"); 214 } 215 return value; 216 } 217 218 /** 219 * Unbind a resource for the given key from the current thread. 220 * @param key the key to unbind (usually the resource factory) 221 * @return the previously bound value, or {@code null} if none bound 222 */ 223 @Nullable 224 public static Object unbindResourceIfPossible(Object key) { 225 Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key); 226 return doUnbindResource(actualKey); 227 } 228 229 /** 230 * Actually remove the value of the resource that is bound for the given key. 231 */ 232 @Nullable 233 private static Object doUnbindResource(Object actualKey) { 234 Map<Object, Object> map = resources.get(); 235 if (map == null) { 236 return null; 237 } 238 Object value = map.remove(actualKey); 239 // Remove entire ThreadLocal if empty... 240 if (map.isEmpty()) { 241 resources.remove(); 242 } 243 // Transparently suppress a ResourceHolder that was marked as void... 244 if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) { 245 value = null; 246 } 247 if (value != null && logger.isTraceEnabled()) { 248 logger.trace("Removed value [" + value + "] for key [" + actualKey + "] from thread [" + 249 Thread.currentThread().getName() + "]"); 250 } 251 return value; 252 } 253 254 255 //------------------------------------------------------------------------- 256 // Management of transaction synchronizations 257 //------------------------------------------------------------------------- 258 259 /** 260 * Return if transaction synchronization is active for the current thread. 261 * Can be called before register to avoid unnecessary instance creation. 262 * @see #registerSynchronization 263 */ 264 public static boolean isSynchronizationActive() { 265 return (synchronizations.get() != null); 266 } 267 268 /** 269 * Activate transaction synchronization for the current thread. 270 * Called by a transaction manager on transaction begin. 271 * @throws IllegalStateException if synchronization is already active 272 */ 273 public static void initSynchronization() throws IllegalStateException { 274 if (isSynchronizationActive()) { 275 throw new IllegalStateException("Cannot activate transaction synchronization - already active"); 276 } 277 logger.trace("Initializing transaction synchronization"); 278 synchronizations.set(new LinkedHashSet<>()); 279 } 280 281 /** 282 * Register a new transaction synchronization for the current thread. 283 * Typically called by resource management code. 284 * <p>Note that synchronizations can implement the 285 * {@link org.springframework.core.Ordered} interface. 286 * They will be executed in an order according to their order value (if any). 287 * @param synchronization the synchronization object to register 288 * @throws IllegalStateException if transaction synchronization is not active 289 * @see org.springframework.core.Ordered 290 */ 291 public static void registerSynchronization(TransactionSynchronization synchronization) 292 throws IllegalStateException { 293 294 Assert.notNull(synchronization, "TransactionSynchronization must not be null"); 295 Set<TransactionSynchronization> synchs = synchronizations.get(); 296 if (synchs == null) { 297 throw new IllegalStateException("Transaction synchronization is not active"); 298 } 299 synchs.add(synchronization); 300 } 301 302 /** 303 * Return an unmodifiable snapshot list of all registered synchronizations 304 * for the current thread. 305 * @return unmodifiable List of TransactionSynchronization instances 306 * @throws IllegalStateException if synchronization is not active 307 * @see TransactionSynchronization 308 */ 309 public static List<TransactionSynchronization> getSynchronizations() throws IllegalStateException { 310 Set<TransactionSynchronization> synchs = synchronizations.get(); 311 if (synchs == null) { 312 throw new IllegalStateException("Transaction synchronization is not active"); 313 } 314 // Return unmodifiable snapshot, to avoid ConcurrentModificationExceptions 315 // while iterating and invoking synchronization callbacks that in turn 316 // might register further synchronizations. 317 if (synchs.isEmpty()) { 318 return Collections.emptyList(); 319 } 320 else { 321 // Sort lazily here, not in registerSynchronization. 322 List<TransactionSynchronization> sortedSynchs = new ArrayList<>(synchs); 323 AnnotationAwareOrderComparator.sort(sortedSynchs); 324 return Collections.unmodifiableList(sortedSynchs); 325 } 326 } 327 328 /** 329 * Deactivate transaction synchronization for the current thread. 330 * Called by the transaction manager on transaction cleanup. 331 * @throws IllegalStateException if synchronization is not active 332 */ 333 public static void clearSynchronization() throws IllegalStateException { 334 if (!isSynchronizationActive()) { 335 throw new IllegalStateException("Cannot deactivate transaction synchronization - not active"); 336 } 337 logger.trace("Clearing transaction synchronization"); 338 synchronizations.remove(); 339 } 340 341 342 //------------------------------------------------------------------------- 343 // Exposure of transaction characteristics 344 //------------------------------------------------------------------------- 345 346 /** 347 * Expose the name of the current transaction, if any. 348 * Called by the transaction manager on transaction begin and on cleanup. 349 * @param name the name of the transaction, or {@code null} to reset it 350 * @see org.springframework.transaction.TransactionDefinition#getName() 351 */ 352 public static void setCurrentTransactionName(@Nullable String name) { 353 currentTransactionName.set(name); 354 } 355 356 /** 357 * Return the name of the current transaction, or {@code null} if none set. 358 * To be called by resource management code for optimizations per use case, 359 * for example to optimize fetch strategies for specific named transactions. 360 * @see org.springframework.transaction.TransactionDefinition#getName() 361 */ 362 @Nullable 363 public static String getCurrentTransactionName() { 364 return currentTransactionName.get(); 365 } 366 367 /** 368 * Expose a read-only flag for the current transaction. 369 * Called by the transaction manager on transaction begin and on cleanup. 370 * @param readOnly {@code true} to mark the current transaction 371 * as read-only; {@code false} to reset such a read-only marker 372 * @see org.springframework.transaction.TransactionDefinition#isReadOnly() 373 */ 374 public static void setCurrentTransactionReadOnly(boolean readOnly) { 375 currentTransactionReadOnly.set(readOnly ? Boolean.TRUE : null); 376 } 377 378 /** 379 * Return whether the current transaction is marked as read-only. 380 * To be called by resource management code when preparing a newly 381 * created resource (for example, a Hibernate Session). 382 * <p>Note that transaction synchronizations receive the read-only flag 383 * as argument for the {@code beforeCommit} callback, to be able 384 * to suppress change detection on commit. The present method is meant 385 * to be used for earlier read-only checks, for example to set the 386 * flush mode of a Hibernate Session to "FlushMode.MANUAL" upfront. 387 * @see org.springframework.transaction.TransactionDefinition#isReadOnly() 388 * @see TransactionSynchronization#beforeCommit(boolean) 389 */ 390 public static boolean isCurrentTransactionReadOnly() { 391 return (currentTransactionReadOnly.get() != null); 392 } 393 394 /** 395 * Expose an isolation level for the current transaction. 396 * Called by the transaction manager on transaction begin and on cleanup. 397 * @param isolationLevel the isolation level to expose, according to the 398 * JDBC Connection constants (equivalent to the corresponding Spring 399 * TransactionDefinition constants), or {@code null} to reset it 400 * @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED 401 * @see java.sql.Connection#TRANSACTION_READ_COMMITTED 402 * @see java.sql.Connection#TRANSACTION_REPEATABLE_READ 403 * @see java.sql.Connection#TRANSACTION_SERIALIZABLE 404 * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_UNCOMMITTED 405 * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_COMMITTED 406 * @see org.springframework.transaction.TransactionDefinition#ISOLATION_REPEATABLE_READ 407 * @see org.springframework.transaction.TransactionDefinition#ISOLATION_SERIALIZABLE 408 * @see org.springframework.transaction.TransactionDefinition#getIsolationLevel() 409 */ 410 public static void setCurrentTransactionIsolationLevel(@Nullable Integer isolationLevel) { 411 currentTransactionIsolationLevel.set(isolationLevel); 412 } 413 414 /** 415 * Return the isolation level for the current transaction, if any. 416 * To be called by resource management code when preparing a newly 417 * created resource (for example, a JDBC Connection). 418 * @return the currently exposed isolation level, according to the 419 * JDBC Connection constants (equivalent to the corresponding Spring 420 * TransactionDefinition constants), or {@code null} if none 421 * @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED 422 * @see java.sql.Connection#TRANSACTION_READ_COMMITTED 423 * @see java.sql.Connection#TRANSACTION_REPEATABLE_READ 424 * @see java.sql.Connection#TRANSACTION_SERIALIZABLE 425 * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_UNCOMMITTED 426 * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_COMMITTED 427 * @see org.springframework.transaction.TransactionDefinition#ISOLATION_REPEATABLE_READ 428 * @see org.springframework.transaction.TransactionDefinition#ISOLATION_SERIALIZABLE 429 * @see org.springframework.transaction.TransactionDefinition#getIsolationLevel() 430 */ 431 @Nullable 432 public static Integer getCurrentTransactionIsolationLevel() { 433 return currentTransactionIsolationLevel.get(); 434 } 435 436 /** 437 * Expose whether there currently is an actual transaction active. 438 * Called by the transaction manager on transaction begin and on cleanup. 439 * @param active {@code true} to mark the current thread as being associated 440 * with an actual transaction; {@code false} to reset that marker 441 */ 442 public static void setActualTransactionActive(boolean active) { 443 actualTransactionActive.set(active ? Boolean.TRUE : null); 444 } 445 446 /** 447 * Return whether there currently is an actual transaction active. 448 * This indicates whether the current thread is associated with an actual 449 * transaction rather than just with active transaction synchronization. 450 * <p>To be called by resource management code that wants to discriminate 451 * between active transaction synchronization (with or without backing 452 * resource transaction; also on PROPAGATION_SUPPORTS) and an actual 453 * transaction being active (with backing resource transaction; 454 * on PROPAGATION_REQUIRED, PROPAGATION_REQUIRES_NEW, etc). 455 * @see #isSynchronizationActive() 456 */ 457 public static boolean isActualTransactionActive() { 458 return (actualTransactionActive.get() != null); 459 } 460 461 462 /** 463 * Clear the entire transaction synchronization state for the current thread: 464 * registered synchronizations as well as the various transaction characteristics. 465 * @see #clearSynchronization() 466 * @see #setCurrentTransactionName 467 * @see #setCurrentTransactionReadOnly 468 * @see #setCurrentTransactionIsolationLevel 469 * @see #setActualTransactionActive 470 */ 471 public static void clear() { 472 synchronizations.remove(); 473 currentTransactionName.remove(); 474 currentTransactionReadOnly.remove(); 475 currentTransactionIsolationLevel.remove(); 476 actualTransactionActive.remove(); 477 } 478 479}