Понимание транзакций Spring с исходного уровня

Spring Boot Spring

Оригинал: Miss Sister Taste (идентификатор публичной учетной записи WeChat: xjjdog), добро пожаловать, пожалуйста, сохраните источник для перепечатки.

Весна имеет5уровень изоляции,7распространяющееся поведение. Это то, о чем часто спрашивают на собеседованиях, и это также знание, которое часто встречается в коде. это знаниетупойа такжескучный, некоторые из которых очень круглые. Жалко было бы на это садиться.

На основе каких-то дел xjjdog обсуждает несколько понятий, о которых легко забыть, находит причины на уровне исходного кода и углубляет наше понимание.Возможно проблемы включают в себя:

  1. Является ли изоляция транзакций Spring и базы данных концепцией?
  2. Как Spring реализует транзакции?
  3. Что такое механизмы изоляции транзакций?
  4. Каковы механизмы распространения транзакций?
  5. Нужно ли оператору запроса открывать транзакцию?
  6. Полезно ли добавлять аннотации транзакций к закрытым методам?

1. Является ли изоляция транзакций Spring и базы данных концепцией?

Давайте начнем с первого вопроса: уровень изоляции транзакций Spring и уровень изоляции транзакций данных — это одно и то же?

На самом деле в базе данных обычно всего 4 механизма изоляции: Spring абстрагирует дефолт, который меняется в зависимости от настроек данных.

  • читать незафиксированные
  • чтение зафиксировано (фиксированное чтение, неповторяемое чтение)
  • повторяемое чтение
  • сериализуемый (сериализуемый)
  • default (уровень изоляции PlatformTransactionManager по умолчанию, с использованием базы данных по умолчанию)

Это связано с тем, что Spring предоставляет только унифицированный интерфейс управления транзакциями, а конкретная реализация реализуется каждой самой базой данных (например, MySQL). Spring настроит уровень изоляции базы данных в соответствии с уровнем изоляции, установленным в текущей среде в начале транзакции, чтобы обеспечить его согласованность.

существуетDataSourceUtilsВ файле код подробно выводит этот процесс.

// Apply specific isolation level, if any.
Integer previousIsolationLevel = null;
if (definition != null && definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
	if (logger.isDebugEnabled()) {
		logger.debug("Changing isolation level of JDBC Connection [" + con + "] to " +
				definition.getIsolationLevel());
	}
	int currentIsolation = con.getTransactionIsolation();
	if (currentIsolation != definition.getIsolationLevel()) {
		previousIsolationLevel = currentIsolation;
		con.setTransactionIsolation(definition.getIsolationLevel());
	}
}

Вывод: в трех случаях, если Spring не указывает уровень изоляции транзакций, будет использоваться уровень изоляции транзакций базы данных по умолчанию; когда Spring указывает уровень изоляции транзакций, уровень изоляции транзакций будет изменен на указанное значение в коде ; когда база данных не поддерживает этот уровень изоляции, эффект зависит от базы данных (например, при использовании механизма MyISAM).

Мы будем использовать следующие способы объявления. Если нет проблем с производительностью и спросом, не меняйте. Обработка транзакций не очень хорошая, и таблица блокировки будет мертва в случае большого слияния.

@Transactional(isolation = Isolation.READ_UNCOMMITTED)

2. 7 механизмов распространения транзакций Spring

Пока вы пишете код, в коде всегда будет вложенность или зацикливание, что приведет к вложенности или зацикливанию транзакций. Тогда транзакция будет реагировать по-разному в этих случаях в зависимости от конфигурации.

  • REQUIREDЭто значение по умолчанию. Указывает, что текущий метод должен выполняться в контексте с транзакцией.Если у клиента выполняется транзакция, вызываемый объект будет выполняться в транзакции, в противном случае транзакция будет перезапущена. (Если на вызываемой стороне возникает исключение, транзакция как на вызывающей, так и на вызываемой стороне будет отменена)
  • REQUIRE_NEWУказывает, что текущий метод должен выполняться в собственной транзакции. Если есть текущая транзакция, текущая транзакция будет приостановлена ​​во время выполнения этого метода.
  • NESTEDЕсли в текущем методе выполняется транзакция, этот метод должен выполняться во вложенной транзакции, которую можно зафиксировать или откатить независимо от инкапсулированной транзакции. Если инкапсулированная транзакция существует, а внешняя транзакция генерирует исключение и выполняет откат, то внутреннюю транзакцию необходимо откатить, иначе внутренняя транзакция не влияет на внешнюю транзакцию. Если инкапсулирующая транзакция не существует, то же самоеrequiredТакой же
  • SUPPORTSУказывает, что текущий метод не обязательно должен иметь контекст транзакции, но он также может выполняться внутри транзакции, если он есть.
  • NOT_SUPPORTEDУказывает, что метод не следует запускать в транзакции. Если транзакция выполняется, она будет приостановлена ​​во время выполнения и не возобновится до тех пор, пока транзакция не будет зафиксирована или отменена.
  • MANDATORYУказывает, что текущий метод должен выполняться в транзакции, если транзакции нет, будет выдано исключение
  • NEVERУказывает, что текущая транзакция не должна выполняться в транзакции, если транзакция есть, выдать исключение

Обычно используется более REQUIRED,REQUIRES_NEW, используйте другие, вы должны быть осторожны, понять, а затем использовать.

больше всего боится如果Эти два слова очень усложнят ситуацию, особенно когда объем кода велик, никогда не знаешь, что написал.serviceкто будет использоваться. Это раздражительно.

Мы объявим следующим образом. Ввиду того, что распространение транзакций в Spring очень округлое, если функция соответствует требованиям, то используйте дефолтную, иначе это вызовет лишние хлопоты.

@Transactional(propagation=Propagation.REQUIRED) 

3. Как реализован механизм распространения транзакций?

Механизм распространения транзакций кажется волшебным, но на самом деле он прост в использовании.ThreadLocalреализован механизм. Таким образом, если вызывающий метод вызывается в новом потоке, распространение транзакции фактически завершится ошибкой. Это отличается от прозрачной передачи, о которой мы говорили ранее, Spring не выполняет такую ​​обработку.

Ссылаться на:Твоя тоже моя. 3 случая ко многопоточности, прозрачная передача локальных переменных

Следовательно, механизм распространения транзакций можно впечатлить, только перевернув исходный код. Только полагаясь на слова для распространения, многие вещи станут неописуемыми.

Как показано на рисунке, PlatformTransactionManager имеет только три простых абстрактных интерфейса, которые определяют все операции транзакций Spring, включая JDBC.

Мы обычно говорим, что JDBC учитывает только часть этого.

Способ реализации по-прежнему использоватьAOPДостичь, по определенной категорииTransactionAspectSupportбыть реализованным. Видно, что код определяет имя, называемоеtransactionInfoHolderПеременная ThreadLocal, когда она используется, может гарантировать, что полученные переменные непротиворечивы в рамках одного и того же потока.

/**
    * Holder to support the {@code currentTransactionStatus()} method,
	* and to support communication between different cooperating advices
	* (e.g. before and after advice) if the aspect involves more than a
	* single method (as will be the case for around advice).
*/
private static final ThreadLocal<TransactionInfo> transactionInfoHolder =
		new NamedThreadLocal<>("Current aspect-driven transaction");

Конкретная бизнес-логика находится вinvokeWithinTransactionреализовано в. Если вы продолжите следовать вниз, вы найдетеAbstractPlatformTransactionManagerв классеgetTransactionметод.

@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
		throws TransactionException {

	// Use defaults if no transaction definition given.
	TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());

	Object transaction = doGetTransaction();
	boolean debugEnabled = logger.isDebugEnabled();

	if (isExistingTransaction(transaction)) {
		// Existing transaction found -> check propagation behavior to find out how to behave.
		return handleExistingTransaction(def, transaction, debugEnabled);
	}

	// Check definition settings for new transaction.
	if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
		throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
	}

	// No existing transaction found -> check propagation behavior to find out how to proceed.
	if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
		throw new IllegalTransactionStateException(
				"No existing transaction found for transaction marked with propagation 'mandatory'");
	}
	else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
			def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
			def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
		SuspendedResourcesHolder suspendedResources = suspend(null);
		if (debugEnabled) {
			logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
		}
		try {
			return startTransaction(def, transaction, debugEnabled, suspendedResources);
		}
		catch (RuntimeException | Error ex) {
			resume(null, suspendedResources);
			throw ex;
		}
	}
	else {
		// Create "empty" transaction: no actual transaction, but potentially synchronization.
		if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
			logger.warn("Custom isolation level specified but no actual transaction initiated; " +
					"isolation level will effectively be ignored: " + def);
		}
		boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
		return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
	}
}

Мне не нужно слишком много объяснять, вся очевидная логика в коде. Транзакции созданы здесь.

4. Может ли метод запроса не запускать транзакцию?

бизнес имеетreadonly, который управляет атрибутом транзакции только для чтения и не имеет ничего общего с тем, открыта транзакция или нет.

В предыдущем посте я говорил об управлении маршрутизацией операторов с помощью установки свойства readonly:«Официальный драйвер MySQL» Тайна разделения Master-Slave (Грамотность), который использует одно из свойств транзакцииreadonly, что в конечном итоге отражается на уровне подключения к базе данных.

connection.setReadOnly(true);

Использование в Spring выглядит следующим образом:

@Transactional(readOnly=true)

Стоит отметить, что после установки этого свойства не каждая базовая база данных поддерживает его. ORM или драйвер среднего слоя тоже могут делать какие-то статьи об этом атрибуте, так что речь не столько об этомreadonlyявляется функциональным, а не暗示.

Если взять MySQL в качестве примера, есть два режима фиксации:

  • SET AUTOCOMMIT=0 отключить автоматическую фиксацию
  • Установить AutoCommit = 1 Включить автоматический коммит

Это все реальные операторы SQL, поэтому, если транзакция включена,AUTOCOMMITбытьfalse. Мы видим, что Spring выполняет следующие операции.

con.setAutoCommit(false);

Если это транзакция только для чтения, не забудьте установить ее вручную.

if (isEnforceReadOnly() && definition.isReadOnly()) {
	try (Statement stmt = con.createStatement()) {
		stmt.executeUpdate("SET TRANSACTION READ ONLY");
	}
}

Такая операция очень дорогая, если аннотация Transaction не добавлена, транзакция не включена по умолчанию. Нет необходимости запускать транзакцию для одного оператора запроса, и конфигурация базы данных по умолчанию может удовлетворить требования.

Однако при одновременном выполнении нескольких операторов запроса, таких как статистический запрос и запрос отчета, в этом сценарии множественный SQL-запрос должен обеспечивать общую согласованность чтения.В противном случае после предыдущего SQL-запроса и до следующего SQL-запроса данные изменяются. другими пользователями приведет к несогласованности данных.

Только в этом случае открывать транзакцию чтения.

5. Полезно ли добавлять аннотации транзакций к закрытым методам?

@TransactionДобавлена ​​аннотацияprivate, и это бесполезно.

Это не особенность кода обработки транзакций для написания. Благодаря этим функциям транзакцийAOPпуть наложен, поэтому он получает управление динамического прокси.

privateа такжеfinalДекорированный метод не будет проксироваться.

但是,你却可以把private方法放在带有事务功能的public方法里。 Таким образом, это看起来Также есть некоторые функциональные особенности транзакций, но это не так.

End

В интернете используется не так много транзакций.Многие из них очень маленькие и очень быстрые интерфейсы.Для разработчиков транзакции являются обузой.

Но в некоторых компаниях с финансовыми атрибутами или в некоторых приложениях для разработки корпоративного уровня транзакции действительно являются препятствием, которое нельзя обойти. Как только вы углубитесь в нее, вы найдете эту точку знаний, и вы будете ждать, пока король войдет в урну.

xjjdogНа уровне исходного кода я рассказал о нескольких часто задаваемых вопросах в интервью. Не удивляйтесь, некоторые люди брали интервью с такими терминами, как грязное чтение и фантомное чтение.

И эти вещи относились к содержанию, которое я вдруг осознал, увидев в то время, и продолжал путать на следующий день.

Когда мы можем быть прагматиками?

Мой блог скоро будет перемещен и синхронизирован с сообществом открытого кода OSCHINA. Это мой OSCHIN ID: Miss Sister Taste. Приглашаю всех присоединиться к нам:woohoo.OSCHINA.net/sharing-para…

Об авторе:Мисс сестра вкус(xjjdog), публичная учетная запись, которая не позволяет программистам идти в обход. Сосредоточьтесь на инфраструктуре и Linux. Десять лет архитектуры, десятки миллиардов ежедневного трафика, обсуждение с вами мира высокой параллелизма, дающие вам другой вкус. Мой личный WeChat xjjdog0, добро пожаловать в друзья для дальнейшего общения.​