preface
Detailed serialization of Spring transactions
[detailed explanation of Spring transactions] - 1 Case demonstration of transaction propagation
[detailed explanation of Spring transactions] - 2 Considerations for transaction application
[detailed explanation of Spring transactions] - 3 Eight scenarios of transaction invalidation
[detailed explanation of Spring transactions] - 4 Architecture analysis of transaction manager
The main function of transaction synchronization manager is to manage the resource and transaction synchronization of each thread, including resource binding, activating transaction synchronization, etc.
6 ThreadLocal
Six ThreadLocal objects are defined in the transaction synchronization manager. They can be analyzed from the name alone. They are responsible for maintaining: transaction resources, transaction synchronization, transaction name, transaction read-only status, transaction isolation level and whether the current transaction is active.
private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources"); private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal<>("Transaction synchronizations"); private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal<>("Current transaction name"); private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal<>("Current transaction read-only status"); private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal<>("Current transaction isolation level"); private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal<>("Actual transaction active");
Transaction resources
For transaction resources, the transaction synchronization manager is responsible for providing interfaces such as resource acquisition, binding and unbinding
Get resources
/** * Retrieve a resource for the given key that is bound to the current thread. * @param key the key to check (usually the resource factory) * @return a value bound to the current thread (usually the active * resource object), or {@code null} if none * @see ResourceTransactionManager#getResourceFactory() */ @Nullable public static Object getResource(Object key) { Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key); return doGetResource(actualKey); } /** * Actually check the value of the resource that is bound for the given key. */ @Nullable private static Object doGetResource(Object actualKey) { Map<Object, Object> map = resources.get(); if (map == null) { return null; } Object value = map.get(actualKey); // Transparently remove ResourceHolder that was marked as void... if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) { map.remove(actualKey); // Remove entire ThreadLocal if empty... if (map.isEmpty()) { resources.remove(); } value = null; } return value; }
Binding resources
public static void bindResource(Object key, Object value) throws IllegalStateException { Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key); Assert.notNull(value, "Value must not be null"); Map<Object, Object> map = resources.get(); // set ThreadLocal Map if none found if (map == null) { map = new HashMap<>(); resources.set(map); } Object oldValue = map.put(actualKey, value); // Transparently suppress a ResourceHolder that was marked as void... if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) { oldValue = null; } if (oldValue != null) { throw new IllegalStateException( "Already value [" + oldValue + "] for key [" + actualKey + "] bound to thread"); } }
Unbind resources
public static Object unbindResource(Object key) throws IllegalStateException { Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key); Object value = doUnbindResource(actualKey); if (value == null) { throw new IllegalStateException("No value for key [" + actualKey + "] bound to thread"); } return value; }
@Nullable private static Object doUnbindResource(Object actualKey) { Map<Object, Object> map = resources.get(); if (map == null) { return null; } Object value = map.remove(actualKey); // Remove entire ThreadLocal if empty... if (map.isEmpty()) { resources.remove(); } // Transparently suppress a ResourceHolder that was marked as void... if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) { value = null; } return value; }
Transaction synchronization
For transaction synchronization, the transaction synchronization manager is responsible for providing transaction synchronization activation, transaction synchronization registration, obtaining transaction synchronization list, judging whether the current transaction synchronization is active, etc.
Transaction synchronization activation
It's actually putting a LinkedHashSet into synchronizations
public static void initSynchronization() throws IllegalStateException { if (isSynchronizationActive()) { throw new IllegalStateException("Cannot activate transaction synchronization - already active"); } synchronizations.set(new LinkedHashSet<>()); }
Transaction synchronization registration
Take the set collection from the current thread and add a TransactionSynchronization object to it
public static void registerSynchronization(TransactionSynchronization synchronization) throws IllegalStateException { Assert.notNull(synchronization, "TransactionSynchronization must not be null"); Set<TransactionSynchronization> synchs = synchronizations.get(); if (synchs == null) { throw new IllegalStateException("Transaction synchronization is not active"); } synchs.add(synchronization); }
Get transaction synchronization list
public static List<TransactionSynchronization> getSynchronizations() throws IllegalStateException { Set<TransactionSynchronization> synchs = synchronizations.get(); if (synchs == null) { throw new IllegalStateException("Transaction synchronization is not active"); } // Return unmodifiable snapshot, to avoid ConcurrentModificationExceptions // while iterating and invoking synchronization callbacks that in turn // might register further synchronizations. if (synchs.isEmpty()) { return Collections.emptyList(); } else { // Sort lazily here, not in registerSynchronization. List<TransactionSynchronization> sortedSynchs = new ArrayList<>(synchs); OrderComparator.sort(sortedSynchs); return Collections.unmodifiableList(sortedSynchs); } }
Judge whether the current transaction is active
public static boolean isSynchronizationActive() { return (synchronizations.get() != null); }
currentTransactionReadOnly and currentTransactionIsolationLevel
These two properties record whether the current thread transaction is read-only and its isolation level
public static void setCurrentTransactionReadOnly(boolean readOnly) { currentTransactionReadOnly.set(readOnly ? Boolean.TRUE : null); }
public static void setCurrentTransactionIsolationLevel(@Nullable Integer isolationLevel) { currentTransactionIsolationLevel.set(isolationLevel); }
actualTransactionActive
actualTransactionActive is used to record whether the current thread has actually active transactions,
public static void setActualTransactionActive(boolean active) { actualTransactionActive.set(active ? Boolean.TRUE : null); }
Compared with isSynchronizationActive, the thread may have active transactions, but if the transaction of the current thread is suspended, it is not actually active. Therefore, for the suspended thread, calling isSynchronizationActive returns true, and calling isActualTransactionActive returns false
public static boolean isActualTransactionActive() { return (actualTransactionActive.get() != null); }
eliminate
Finally, after the transaction is committed, the transaction synchronization manager provides a method to clear the transaction synchronization data of the current thread.
public static void clear() { synchronizations.remove(); currentTransactionName.remove(); currentTransactionReadOnly.remove(); currentTransactionIsolationLevel.remove(); actualTransactionActive.remove(); }