[Spring transaction details] - 5 Transaction manager transaction synchronization manager analysis

Posted by twigletmac on Mon, 17 Jan 2022 10:38:23 +0100

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();
}

Topics: Java Spring Back-end