【Перевод】Уровень изоляции транзакций и поведение распространения аннотации @Transcational в Spring

Java

посетить оригинал

предисловие

В этом руководстве мы изучим уровень изоляции транзакций и поведение распространения аннотации @Transactional.

Что такое аннотация @Transcational?

мы можем использовать@TransactionalАннотация добавляет в метод поддержку транзакций.

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

Spring создает, фиксирует и откатывает транзакции, создавая прокси-объекты или манипулируя байт-кодами.

В случае управления транзакциями через режим прокси, для вызовов изнутри класса@Transactionalне удастся.

Проще говоря, если у нас естьcallMethodметод и метод@TransactionalАннотация отмечена.

Spring завершит этот метод некоторым кодом для управления транзакциями,@TransactionalРеализация аннотации может быть представлена ​​с помощью следующего псевдокода:

// 如有必要则创建事务
createTransactionIfNecessary();
try {
    // 调用callMethod方法
    callMethod();
    // 调用成功则提交事务
    commitTransactionAfterReturning();
} catch (exception) {
    // 调用失败则回滚事务
    completeTransactionAfterThrowing();
    throw exception;
}

Как использовать аннотацию @Transcational

Мы можем использовать эту аннотацию для интерфейсов, классов и методов, а также для интерфейсов, классов и методов.@TransactionalАннотации будут перезаписываться в соответствии с разными приоритетами. Самый низкий приоритет будет перезаписан более высоким. Приоритет переопределения от низкого к высокому:

  • интерфейс
  • суперкласс
  • Добрый
  • метод интерфейса
  • метод суперкласса
  • метод класса

Весна отметит класс на@TransactionalАннотация относится ко всему классуpublicметод, поэтому нам не нужно маркировать метод отдельно@Transactional.

Однако, если мыprivateилиprotectedЕсли метод помечен аннотациями @Transcational, то Spring проигнорирует эти аннотации и не выдаст никаких сообщений об ошибках.

Начнем с аннотирования интерфейса аннотацией @Transcational:

@Transactional
public interface TransferService {
    void transfer(String user1, String user2, double val);
}

Обычно не рекомендуется отмечать на интерфейсе@TransactionalАннотацию, однако допустимо использовать в некоторых интерфейсах, таких как: Spring Data@Repositoryинтерфейс.

Мы можем аннотировать эту аннотацию в определении класса, которая может переопределить аннотацию в интерфейсе или суперклассе.@Transactionalаннотация:

@Service
@Transactional
public class TransferServiceImpl implements TransferService {
    @Override
    public void transfer(String user1, String user2, double val) {
        // ...
    }
}

Теперь давайте поместим эту аннотацию непосредственно в определение метода:

@Transactional
public void transfer(String user1, String user2, double val) {
    // ...
}

Поведение распространения транзакции

Поведение распространения транзакции определяет границу транзакции нашей бизнес-логики, и Spring запускает или прерывает транзакцию в соответствии с поведением распространения транзакции, которое мы настраиваем.

Весенние звонкиTransactionManager::getTransactionЗатем метод получает или создает транзакцию на основе поведения распространения транзакции, которая поддерживает некоторые изTransactionManagerПоведение распространения транзакций определено в .TransactionManagerРеализовать для поддержки.

REQUIRED

REQUIRED— поведение распространения транзакций по умолчанию. Для этого поведения распространения Spring проверяет, есть ли активная транзакция в контексте текущего потока.

Новая транзакция создается, если нет активной транзакции.

Если есть активная транзакция, текущая бизнес-логика включается в область действия этой активной транзакции:

@Transactional(propagation = Propagation.REQUIRED)
public void requiredExample(String user) { 
    // ... 
}

из-заREQUIREDявляется поведением распространения транзакций по умолчанию, поэтому приведенный выше код можно сократить как:

@Transactional
public void requiredExample(String user) { 
    // ... 
}

Давайте рассмотрим созданиеREQUIREDПсевдокод для транзакции поведения распространения:

 // 检测是否已经位于事务中
 if (isExistingTransaction()) {
    if (isValidateExistingTransaction()) {
        validateExisitingAndThrowExceptionIfNotValid();
    }
    // 返回已有的事务
    return existing;
}
// 否则不使用事务
return createNewTransaction();

SUPPORTS

заSUPPORTSДругими словами, Spring сначала проверит, есть ли активная транзакция в контексте потока, и использует ее, если активная транзакция есть. Если нет, выполните его без транзакцииsupportsExample()метод::

@Transactional(propagation = Propagation.SUPPORTS)
public void supportsExample(String user) { 
    // ... 
}

Давайте посмотрим на использованиеSUPPORTSПсевдокод поведения распространения для создания транзакции:

// 检测是否已经位于事务中
if (isExistingTransaction()) {
    if (isValidateExistingTransaction()) {
        validateExisitingAndThrowExceptionIfNotValid();
    }
    // 返回已有的事务
    return existing;
}
// 否则不使用事务
return emptyTransaction;

MANDATORY

Когда поведение распространения установлено наMANDATORY, если Spring не найдет активной транзакции в контексте потока, он выдаст исключение:

@Transactional(propagation = Propagation.MANDATORY)
public void mandatoryExample(String user) { 
    // ... 
}

Давайте посмотрим на псевдокодовое представление этой опции:

// 检测是否已经位于事务中
if (isExistingTransaction()) 
{
    if (isValidateExistingTransaction()) {
        validateExisitingAndThrowExceptionIfNotValid();
    }
    // 返回已有的事务
    return existing;
}
// 否则抛出异常
throw IllegalTransactionStateException;

NEVER

Когда поведение распространения установлено наNEVER, Spring выдает исключение, когда находит активную транзакцию в контексте потока:

@Transactional(propagation = Propagation.NEVER)
public void neverExample(String user) { 
    // ... 
}

Давайте посмотрим на использованиеNEVERПсевдокод поведения распространения для создания транзакции:

// 检测是否已经位于事务中
if (isExistingTransaction()) {
    // 如果位于事务中就抛出异常
    throw IllegalTransactionStateException;
}
// 否则不使用事务
return emptyTransaction;

NOT_SUPPORTED

Когда поведение распространения установлено наNOT_SUPPORTEDКогда Spring приостанавливает существующую транзакцию в контексте потока, а затем выполняет ее без использования транзакцииnotSupportedExample()метод:

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void notSupportedExample(String user) { 
    // ... 
}

JTATransactionManagerМенеджер транзакций поддерживает настоящую приостановку транзакций по умолчанию. разное Диспетчер транзакций имитирует приостановку транзакции, сохраняя ссылку на существующую транзакцию, а затем удаляя ее из контекста потока.

REQUIRES_NEW

Когда поведение распространения установлено наREQUIRES_NEWКогда Spring обнаруживает, что в контексте потока есть активная транзакция, он сначала приостанавливает ее, а затем создает новую транзакцию для выполнения.requiresNewExample()метод:

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void requiresNewExample(String user) { 
    // ... 
}

а такжеNOT_SUPPORTEDВарианты аналогичны, для истинной приостановки транзакции нужно использоватьJTATransactionManagerменеджер транзакций

Реализация псевдокода, соответствующая этой опции:


// 检测是否已经位于事务中
if (isExistingTransaction()) {
    // 先挂起已存在的事务
    suspend(existing);
    try {
        // 然后创建一个新事务
        return createNewTransaction();
    } catch (exception) {
        // 如果新事务发生异常则恢复旧事务
        resumeAfterBeginException();
        throw exception;
    }
}
// 没有找到旧事务,直接创建新事务
return createNewTransaction();

NESTED

заNESTEDНапример, Spring определяет, есть ли активная транзакция в контексте потока.

Устанавливает точку сохранения в транзакции, если она уже существует, что означает, что если нашаnestedExample()Если в методе возникнет исключение, транзакция будет отброшена к установленной нами точке сохранения.

Если существование активной транзакции не обнаружено, эта опция работает так же, какREQUIREDВарианты те же.

DataSourceTransactionManagerЭтот вариант поддерживается из коробки. также,JTATransactionManagerНекоторые реализации также поддерживают эту опцию.

JpaTransactionManagerПоддерживается только для соединений JDBCNESTEDопции. Однако, если мы будемnestedTransactionAllowedустановлен флагtrue, а наш драйвер JDBC также поддерживает функцию точки сохранения, она также работает в транзакциях JPA.JDBCкод доступа.

Наконец, давайте установим поведение распространенияNESTED:

@Transactional(propagation = Propagation.NESTED)
public void nestedExample(String user) { 
    // ... 
}

Уровень изоляции транзакции

Изоляция является одним из свойств ACID:

  • атомарность
  • Последовательность
  • Изоляция
  • Долговечность

Изоляция относится к видимости данных между параллельными транзакциями.

Каждый уровень изоляции предотвращает появление ноля или более параллельных побочных эффектов в параллельных транзакциях:

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

мы можем пройти@Transactional::isolationУстанавливает уровень изоляции транзакции. В Spring он может использовать следующие пять значений перечисления:

  • DEFAULT
  • READ_UNCOMMITTED
  • READ_COMMITTED
  • REPEATABLE_READ
  • SERIALIZABLE

Управление уровнем изоляции в Spring

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

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

Обычно уровень изоляции транзакции также устанавливается при создании транзакции.Если мы не хотим, чтобы методы в нашей цепочке вызовов имели разные уровни изоляции транзакции, мы можем установитьTransactionManager::setValidateExistingTransactionзаtrue, его функция выражается в псевдокоде как:

// 如果隔离级别不是DEFAULT
if (isolationLevel != ISOLATION_DEFAULT) {
    // 当前事务设置的隔离级别与已存在事务的隔离级别不一致则抛出异常
    if (currentTransactionIsolationLevel() != isolationLevel) {
        throw IllegalTransactionStateException
    }
}

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

READ_UNCOMMITTED

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

Мы можем установить этот уровень изоляции для методов или классов:

@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void log(String message) {
    // ...
}

Постгрес не поддерживаетREAD_UNCOMMITTEDуровень изоляции, который используетREAD_COMMITEDУровень изоляции как альтернатива. Оракл не поддерживаетREAD_UNCOMMITTEDуровень.

READ_COMMITTED

Второй уровень изоляцииREAD_COMMITTED. Это предотвращает грязное чтение.

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

Следующий код устанавливает уровень изоляции транзакции наREAD_COMMITTED:

@Transactional(isolation = Isolation.READ_COMMITTED)
public void log(String message){
    // ...
}

REPEATABLE_READ

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

Кроме того, когда мы повторно запрашиваем строку данных, мы не получаем других результатов. Но при повторном выполнении запроса диапазона мы можем получить новые добавленные или удаленные строки.

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

Следующий код устанавливает уровень изоляции транзакции наREAD_COMMITTED:

@Transactional(isolation = Isolation.REPEATABLE_READ) 
public void log(String message){
    // ...
}

REPEATABLE_READуровень изоляции по умолчанию в Mysql. Оракл не поддерживаетREPEATABLE_READуровень изоляции.

SERIALIZABLE

SERIALIZABLEЭто высший уровень изоляции. Это предотвращает грязные чтения, неповторяющиеся чтения и фантомные чтения.

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

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

Теперь давайте посмотрим, как установить уровень изоляции наSERIALIZABLE:

@Transactional(isolation = Isolation.SERIALIZABLE)
public void log(String message){
    // ...
}

Суммировать

В этом руководстве мы подробно изучили поведение распространения @Transaction. Затем мы узнали о побочных эффектах и ​​уровнях изоляции параллельных транзакций.

Как всегда, вы можетеGitHubНайдите полный код на .