1 /* 2 * Copyright 2002-2011 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.springframework.transaction.support; 18 19 import java.util.ArrayList; 20 import java.util.Collections; 21 import java.util.HashMap; 22 import java.util.LinkedHashSet; 23 import java.util.List; 24 import java.util.Map; 25 import java.util.Set; 26 27 import org.apache.commons.logging.Log; 28 import org.apache.commons.logging.LogFactory; 29 30 import org.springframework.core.NamedThreadLocal; 31 import org.springframework.core.OrderComparator; 32 import org.springframework.util.Assert; 33 34 /** 35 * Central helper that manages resources and transaction synchronizations per thread. 36 * To be used by resource management code but not by typical application code. 37 * 38 * <p>Supports one resource per key without overwriting, that is, a resource needs 39 * to be removed before a new one can be set for the same key. 40 * Supports a list of transaction synchronizations if synchronization is active. 41 * 42 * <p>Resource management code should check for thread-bound resources, e.g. JDBC 43 * Connections or Hibernate Sessions, via <code>getResource</code>. Such code is 44 * normally not supposed to bind resources to threads, as this is the responsibility 45 * of transaction managers. A further option is to lazily bind on first use if 46 * transaction synchronization is active, for performing transactions that span 47 * an arbitrary number of resources. 48 * 49 * <p>Transaction synchronization must be activated and deactivated by a transaction 50 * manager via {@link #initSynchronization()} and {@link #clearSynchronization()}. 51 * This is automatically supported by {@link AbstractPlatformTransactionManager}, 52 * and thus by all standard Spring transaction managers, such as 53 * {@link org.springframework.transaction.jta.JtaTransactionManager} and 54 * {@link org.springframework.jdbc.datasource.DataSourceTransactionManager}. 55 * 56 * <p>Resource management code should only register synchronizations when this 57 * manager is active, which can be checked via {@link #isSynchronizationActive}; 58 * it should perform immediate resource cleanup else. If transaction synchronization 59 * isn't active, there is either no current transaction, or the transaction manager 60 * doesn't support transaction synchronization. 61 * 62 * <p>Synchronization is for example used to always return the same resources 63 * within a JTA transaction, e.g. a JDBC Connection or a Hibernate Session for 64 * any given DataSource or SessionFactory, respectively. 65 * 66 * @author Juergen Hoeller 67 * @since 02.06.2003 68 * @see #isSynchronizationActive 69 * @see #registerSynchronization 70 * @see TransactionSynchronization 71 * @see AbstractPlatformTransactionManager#setTransactionSynchronization 72 * @see org.springframework.transaction.jta.JtaTransactionManager 73 * @see org.springframework.jdbc.datasource.DataSourceTransactionManager 74 * @see org.springframework.jdbc.datasource.DataSourceUtils#getConnection 75 */ 76 public abstract class TransactionSynchronizationManager { 77 78 private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class); 79 80 private static final ThreadLocal<Map<Object, Object>> resources = 81 new NamedThreadLocal<Map<Object, Object>>("Transactional resources"); 82 83 private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = 84 new NamedThreadLocal<Set<TransactionSynchronization>>("Transaction synchronizations"); 85 86 private static final ThreadLocal<String> currentTransactionName = 87 new NamedThreadLocal<String>("Current transaction name"); 88 89 private static final ThreadLocal<Boolean> currentTransactionReadOnly = 90 new NamedThreadLocal<Boolean>("Current transaction read-only status"); 91 92 private static final ThreadLocal<Integer> currentTransactionIsolationLevel = 93 new NamedThreadLocal<Integer>("Current transaction isolation level"); 94 95 private static final ThreadLocal<Boolean> actualTransactionActive = 96 new NamedThreadLocal<Boolean>("Actual transaction active"); 97 98 99 //------------------------------------------------------------------------- 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</code> 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 */ getResourceMap()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 */ hasResource(Object key)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</code> if none 134 * @see ResourceTransactionManager#getResourceFactory() 135 */ getResource(Object key)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 */ doGetResource(Object actualKey)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 */ bindResource(Object key, Object value)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 */ unbindResource(Object key)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</code> if none bound 219 */ unbindResourceIfPossible(Object key)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 */ doUnbindResource(Object actualKey)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 */ isSynchronizationActive()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 */ initSynchronization()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 */ registerSynchronization(TransactionSynchronization synchronization)286 public static void registerSynchronization(TransactionSynchronization synchronization) 287 throws IllegalStateException { 288 289 Assert.notNull(synchronization, "TransactionSynchronization must not be null"); 290 if (!isSynchronizationActive()) { 291 throw new IllegalStateException("Transaction synchronization is not active"); 292 } 293 synchronizations.get().add(synchronization); 294 } 295 296 /** 297 * Return an unmodifiable snapshot list of all registered synchronizations 298 * for the current thread. 299 * @return unmodifiable List of TransactionSynchronization instances 300 * @throws IllegalStateException if synchronization is not active 301 * @see TransactionSynchronization 302 */ getSynchronizations()303 public static List<TransactionSynchronization> getSynchronizations() throws IllegalStateException { 304 Set<TransactionSynchronization> synchs = synchronizations.get(); 305 if (synchs == null) { 306 throw new IllegalStateException("Transaction synchronization is not active"); 307 } 308 // Return unmodifiable snapshot, to avoid ConcurrentModificationExceptions 309 // while iterating and invoking synchronization callbacks that in turn 310 // might register further synchronizations. 311 if (synchs.isEmpty()) { 312 return Collections.emptyList(); 313 } 314 else { 315 // Sort lazily here, not in registerSynchronization. 316 List<TransactionSynchronization> sortedSynchs = new ArrayList<TransactionSynchronization>(synchs); 317 OrderComparator.sort(sortedSynchs); 318 return Collections.unmodifiableList(sortedSynchs); 319 } 320 } 321 322 /** 323 * Deactivate transaction synchronization for the current thread. 324 * Called by the transaction manager on transaction cleanup. 325 * @throws IllegalStateException if synchronization is not active 326 */ clearSynchronization()327 public static void clearSynchronization() throws IllegalStateException { 328 if (!isSynchronizationActive()) { 329 throw new IllegalStateException("Cannot deactivate transaction synchronization - not active"); 330 } 331 logger.trace("Clearing transaction synchronization"); 332 synchronizations.remove(); 333 } 334 335 336 //------------------------------------------------------------------------- 337 // Exposure of transaction characteristics 338 //------------------------------------------------------------------------- 339 340 /** 341 * Expose the name of the current transaction, if any. 342 * Called by the transaction manager on transaction begin and on cleanup. 343 * @param name the name of the transaction, or <code>null</code> to reset it 344 * @see org.springframework.transaction.TransactionDefinition#getName() 345 */ setCurrentTransactionName(String name)346 public static void setCurrentTransactionName(String name) { 347 currentTransactionName.set(name); 348 } 349 350 /** 351 * Return the name of the current transaction, or <code>null</code> if none set. 352 * To be called by resource management code for optimizations per use case, 353 * for example to optimize fetch strategies for specific named transactions. 354 * @see org.springframework.transaction.TransactionDefinition#getName() 355 */ getCurrentTransactionName()356 public static String getCurrentTransactionName() { 357 return currentTransactionName.get(); 358 } 359 360 /** 361 * Expose a read-only flag for the current transaction. 362 * Called by the transaction manager on transaction begin and on cleanup. 363 * @param readOnly <code>true</code> to mark the current transaction 364 * as read-only; <code>false</code> to reset such a read-only marker 365 * @see org.springframework.transaction.TransactionDefinition#isReadOnly() 366 */ setCurrentTransactionReadOnly(boolean readOnly)367 public static void setCurrentTransactionReadOnly(boolean readOnly) { 368 currentTransactionReadOnly.set(readOnly ? Boolean.TRUE : null); 369 } 370 371 /** 372 * Return whether the current transaction is marked as read-only. 373 * To be called by resource management code when preparing a newly 374 * created resource (for example, a Hibernate Session). 375 * <p>Note that transaction synchronizations receive the read-only flag 376 * as argument for the <code>beforeCommit</code> callback, to be able 377 * to suppress change detection on commit. The present method is meant 378 * to be used for earlier read-only checks, for example to set the 379 * flush mode of a Hibernate Session to "FlushMode.NEVER" upfront. 380 * @see org.springframework.transaction.TransactionDefinition#isReadOnly() 381 * @see TransactionSynchronization#beforeCommit(boolean) 382 */ isCurrentTransactionReadOnly()383 public static boolean isCurrentTransactionReadOnly() { 384 return (currentTransactionReadOnly.get() != null); 385 } 386 387 /** 388 * Expose an isolation level for the current transaction. 389 * Called by the transaction manager on transaction begin and on cleanup. 390 * @param isolationLevel the isolation level to expose, according to the 391 * JDBC Connection constants (equivalent to the corresponding Spring 392 * TransactionDefinition constants), or <code>null</code> to reset it 393 * @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED 394 * @see java.sql.Connection#TRANSACTION_READ_COMMITTED 395 * @see java.sql.Connection#TRANSACTION_REPEATABLE_READ 396 * @see java.sql.Connection#TRANSACTION_SERIALIZABLE 397 * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_UNCOMMITTED 398 * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_COMMITTED 399 * @see org.springframework.transaction.TransactionDefinition#ISOLATION_REPEATABLE_READ 400 * @see org.springframework.transaction.TransactionDefinition#ISOLATION_SERIALIZABLE 401 * @see org.springframework.transaction.TransactionDefinition#getIsolationLevel() 402 */ setCurrentTransactionIsolationLevel(Integer isolationLevel)403 public static void setCurrentTransactionIsolationLevel(Integer isolationLevel) { 404 currentTransactionIsolationLevel.set(isolationLevel); 405 } 406 407 /** 408 * Return the isolation level for the current transaction, if any. 409 * To be called by resource management code when preparing a newly 410 * created resource (for example, a JDBC Connection). 411 * @return the currently exposed isolation level, according to the 412 * JDBC Connection constants (equivalent to the corresponding Spring 413 * TransactionDefinition constants), or <code>null</code> if none 414 * @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED 415 * @see java.sql.Connection#TRANSACTION_READ_COMMITTED 416 * @see java.sql.Connection#TRANSACTION_REPEATABLE_READ 417 * @see java.sql.Connection#TRANSACTION_SERIALIZABLE 418 * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_UNCOMMITTED 419 * @see org.springframework.transaction.TransactionDefinition#ISOLATION_READ_COMMITTED 420 * @see org.springframework.transaction.TransactionDefinition#ISOLATION_REPEATABLE_READ 421 * @see org.springframework.transaction.TransactionDefinition#ISOLATION_SERIALIZABLE 422 * @see org.springframework.transaction.TransactionDefinition#getIsolationLevel() 423 */ getCurrentTransactionIsolationLevel()424 public static Integer getCurrentTransactionIsolationLevel() { 425 return currentTransactionIsolationLevel.get(); 426 } 427 428 /** 429 * Expose whether there currently is an actual transaction active. 430 * Called by the transaction manager on transaction begin and on cleanup. 431 * @param active <code>true</code> to mark the current thread as being associated 432 * with an actual transaction; <code>false</code> to reset that marker 433 */ setActualTransactionActive(boolean active)434 public static void setActualTransactionActive(boolean active) { 435 actualTransactionActive.set(active ? Boolean.TRUE : null); 436 } 437 438 /** 439 * Return whether there currently is an actual transaction active. 440 * This indicates whether the current thread is associated with an actual 441 * transaction rather than just with active transaction synchronization. 442 * <p>To be called by resource management code that wants to discriminate 443 * between active transaction synchronization (with or without backing 444 * resource transaction; also on PROPAGATION_SUPPORTS) and an actual 445 * transaction being active (with backing resource transaction; 446 * on PROPAGATION_REQUIRES, PROPAGATION_REQUIRES_NEW, etc). 447 * @see #isSynchronizationActive() 448 */ isActualTransactionActive()449 public static boolean isActualTransactionActive() { 450 return (actualTransactionActive.get() != null); 451 } 452 453 454 /** 455 * Clear the entire transaction synchronization state for the current thread: 456 * registered synchronizations as well as the various transaction characteristics. 457 * @see #clearSynchronization() 458 * @see #setCurrentTransactionName 459 * @see #setCurrentTransactionReadOnly 460 * @see #setCurrentTransactionIsolationLevel 461 * @see #setActualTransactionActive 462 */ clear()463 public static void clear() { 464 clearSynchronization(); 465 setCurrentTransactionName(null); 466 setCurrentTransactionReadOnly(false); 467 setCurrentTransactionIsolationLevel(null); 468 setActualTransactionActive(false); 469 } 470 471 } 472