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