Распространение транзакций и изоляция весной @Transactional
1. Введение
В этом учебнике мы покроем @Transactional аннотация и ее изоляция и распространение Параметры.
2. Что такое @Transactional?
Мы можем использовать @Transactional обернуть метод в транзакцию базы данных.
Это позволяет нам устанавливать условия распространения, изоляции, тайм-аута, только для чтения и отката для нашей транзакции. Также можно указать менеджера транзакций.
2.1. @Transactional подробности осуществления
Spring создает прокси или манипулирует кодом класса для управления созданием, коммитом и откатом транзакции. В случае прокси, Весна игнорирует @Transactional во внутренних вызовов метода.
Проще говоря, если у нас есть метод, как callMethod и мы отмечаем его как @Transactional, Весна обернет некоторый код управления транзакцией вокруг призыва: @Transactional метод под названием:
createTransactionIfNecessary(); try { callMethod(); commitTransactionAfterReturning(); } catch (exception) { completeTransactionAfterThrowing(); throw exception; }
2.2. Как использовать @Transactional
Мы можем поместить аннотацию на определения интерфейсов, классов или непосредственно на методы. Они переопределяют друг друга в соответствии с приоритетом порядка; от самого низкого до самого высокого у нас есть: интерфейс, суперкласс, класс, метод интерфейса, метод суперкласса и метод класса.
Весна применяет аннотацию уровня класса ко всем общедоступным методам этого класса, которые мы не аннотировать @Transactional .
Однако, если мы ставим аннотацию на частный или защищенный метод, Spring проигнорирует ее без ошибки.
Начнем с образца интерфейса:
@Transactional public interface TransferService { void transfer(String user1, String user2, double val); }
Как правило, не рекомендуется устанавливать @Transactional на интерфейсе. Тем не менее, это приемлемо для таких случаев, как @Repository с весенними данными.
Мы можем поместить аннотацию в определение класса, чтобы переопределить параметр транзакции интерфейса/суперкласса:
@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) { // ... }
3. Распространение транзакций
Распространение определяет границу транзакций нашей бизнес-логики. Spring удается начать и приостановить транзакцию в соответствии с нашими распространение оправа.
Весенние звонки TransactionManager::getTransaction получить или создать транзакцию в соответствии с распространением. Он поддерживает некоторые из распространения для всех типов ТранзакцияМенеджер , но есть несколько из них, которые только поддерживается конкретными реализациями ТранзакцияМенеджер .
Теперь давайте рассмотрим различные распространения и как они работают.
3.1. РАСПРОСТРАНЕНИЕ REQUIRED
ОБЯЗАТЕЛЬНЫе является распространением по умолчанию. Весенние проверки, если есть активная транзакция, то она создает новую, если ничего не существовало. В противном случае бизнес-логика придатки к в настоящее время активной транзакции:
@Transactional(propagation = Propagation.REQUIRED) public void requiredExample(String user) { // ... }
Кроме того, ОБЯЗАТЕЛЬНЫе является распространением по умолчанию, мы можем упростить код, сбросив его:
@Transactional public void requiredExample(String user) { // ... }
Давайте посмотрим псевдо-код того, как создание транзакций работает для ОБЯЗАТЕЛЬНЫе распространение:
if (isExistingTransaction()) { if (isValidateExistingTransaction()) { validateExisitingAndThrowExceptionIfNotValid(); } return existing; } return createNewTransaction();
3.2. Пропаганда SUPPORTS
Для SUPPORTS , Весна сначала проверяет, существует ли активная транзакция. Если транзакция существует, то существующая транзакция будет использоваться. Если транзакции нет, она выполняется не транзакционной:
@Transactional(propagation = Propagation.SUPPORTS) public void supportsExample(String user) { // ... }
Давайте посмотрим псевдокодей создания транзакции для SUPPORTS :
if (isExistingTransaction()) { if (isValidateExistingTransaction()) { validateExisitingAndThrowExceptionIfNotValid(); } return existing; } return emptyTransaction;
3.3. Распространение MANDATORY
Когда распространяется ОБЯЗАТЕЛЬНЫЙ , если есть активная транзакция, то она будет использоваться. Если нет активной транзакции, то Spring делает исключение:
@Transactional(propagation = Propagation.MANDATORY) public void mandatoryExample(String user) { // ... }
И давайте еще раз посмотрим псевдо-код:
if (isExistingTransaction()) { if (isValidateExistingTransaction()) { validateExisitingAndThrowExceptionIfNotValid(); } return existing; } throw IllegalTransactionStateException;
3.4. НИКОГДА не распространяется
Для транзакционной логики с НИКОГДА распространение, Весна бросает исключение, если есть активная транзакция:
@Transactional(propagation = Propagation.NEVER) public void neverExample(String user) { // ... }
Давайте посмотрим псевдо-код того, как создание транзакций работает для НИКОГДА распространение:
if (isExistingTransaction()) { throw IllegalTransactionStateException; } return emptyTransaction;
3.5. NOT_SUPPORTED Распространение
Весна сначала приостанавливает текущую транзакцию, если она существует, затем бизнес-логика выполняется без транзакции.
@Transactional(propagation = Propagation.NOT_SUPPORTED) public void notSupportedExample(String user) { // ... }
тем JTAТрансакцияМенагер поддерживает реальную приостановку транзакций из коробки. Другие моделируют подвеску, удерживая ссылку на существующую, а затем очищая ее из контекста потока
3.6. REQUIRES_NEW Распространение
Когда распространяется REQUIRES_NEW , Spring приостанавливает текущую транзакцию, если она существует, а затем создает новую:
@Transactional(propagation = Propagation.REQUIRES_NEW) public void requiresNewExample(String user) { // ... }
Как и в случае с NOT_SUPPORTED , нам нужно JTAТрансакцияМенагер для фактической приостановки транзакции.
И псевдо-код выглядит так:
if (isExistingTransaction()) { suspend(existing); try { return createNewTransaction(); } catch (exception) { resumeAfterBeginException(); throw exception; } } return createNewTransaction();
3.7. Распространение NESTED
Для НЕ ВЛОЖЕННЫЕ распространение, Spring проверяет, существует ли транзакция, то если да, то она отмечает точку сохранения. Это означает, что если выполнение нашей бизнес-логики делает исключение, то откат транзакций в эту точку сохранения. Если нет активной транзакции, она работает как ОБЯЗАТЕЛЬНЫе .
ДанныеИсточникТрансакцияМенагер поддерживает это распространение из коробки. Кроме того, некоторые реализации JTAТрансакцияМенагер может поддержать это.
JpaТрансакцияМенагер Поддерживает вложенный только для соединений JDBC. Однако, если мы будем вложенныйТрансакцияАслоум флаг истинный , он также работает для кода доступа JDBC в транзакциях JPA, если наш драйвер JDBC поддерживает точки сохранения.
Наконец, давайте распространение НЕ ВЛОЖЕННЫЕ :
@Transactional(propagation = Propagation.NESTED) public void nestedExample(String user) { // ... }
4. Изоляция транзакций
Изоляция является одним из общих свойств ACID: атомачность, последовательность, изоляция и долговечность. Изоляция описывает, как изменения, применяемые параллельными транзакциями, видны друг другу.
Каждый уровень изоляции предотвращает нулевые или более побочные эффекты транзакции:
- Грязные читать: прочитать незавершенную смену параллельной транзакции
- Неповторяемые читать : получить различное значение при повторном считыив строке, если одновременное транзакция обновляет ту же строку и совершает
- Фантом читать: получить различные строки после повторного выполнения запроса диапазона, если другая транзакция добавляет или удаляет некоторые строки в диапазоне и совершает
Мы можем установить уровень изоляции транзакции по @Transactional::изоляция. Он имеет эти пять перечислений весной: ПО умолчанию , READ_UNCOMMITTED , READ_COMMITTED , REPEATABLE_READ , Сериализуемый.
4.1. Управление изоляцией весной
Уровень изоляции по умолчанию ПО умолчанию . Поэтому, когда Spring создат новую транзакцию, уровень изоляции будет изоляцией по умолчанию нашей RDBMS. Поэтому мы должны быть осторожны, если мы изменим базу данных.
Мы должны также рассмотреть случаи, когда мы называем цепочку методов с различной изоляцией . В обычном потоке изоляция применяется только при новой транзакции. Таким образом, если по какой-либо причине мы не хотим, чтобы метод для выполнения в другой изоляции, мы должны установить TransactionManager:setValidateExistingTransaction к истине. Тогда псевдо-код проверки транзакций будет:
if (isolationLevel != ISOLATION_DEFAULT) { if (currentTransactionIsolationLevel() != isolationLevel) { throw IllegalTransactionStateException } }
Теперь давайте глубоко в различных уровнях изоляции и их последствия.
4.2. READ_UNCOMMITTED Изоляция
READ_UNCOMMITTED является самым низким уровнем изоляции и обеспечивает наиболее параллельный доступ.
В результате, он страдает от всех трех упомянутых побочных эффектов concurrency. Таким образом, транзакция с этой изоляцией считывает незавершенные данные других параллельных транзакций. Кроме того, как не повторяемые, так и фантомные чтения могут произойти. Таким образом, мы можем получить другой результат при повторном считыив строку или повторное выполнение запроса диапазона.
Мы можем установить изоляция уровень для метода или класса:
@Transactional(isolation = Isolation.READ_UNCOMMITTED) public void log(String message) { // ... }
Postgres не поддерживает READ_UNCOMMITTED изоляции и падает обратно в READ_COMMITED место . Кроме того, Oracle не поддерживает и не READ_UNCOMMITTED .
4.3. READ_COMMITTED Изоляция
Второй уровень изоляции, READ_COMMITTED, предотвращает грязные читает.
Остальные побочные эффекты concurrency все еще могут случиться. Таким образом, незавершенные изменения в параллельных транзакциях не влияют на нас, но если транзакция совершает свои изменения, наш результат может измениться путем повторного запроса.
Здесь мы устанавливаем изоляция уровень:
@Transactional(isolation = Isolation.READ_COMMITTED) public void log(String message){ // ... }
READ_COMMITTED является уровнем по умолчанию с Postgres, сервером S’L и Oracle.
4.4. REPEATABLE_READ Изоляция
Третий уровень изоляции, REPEATABLE_READ, предотвращает грязные и не повторяемые чтения. Таким образом, на нас не влияют незавершенные изменения в параллельных транзакциях.
Кроме того, когда мы повторно запрос на строку, мы не получаем другой результат. Но при повторном выполнении диапазон-запросов мы можем получить недавно добавленные или удаленные строки.
Кроме того, это самый низкий требуемый уровень для предотвращения потерянного обновления. Потерянное обновление происходит, когда две или более одновременных транзакций считыв и обновляют один и тот же ряд. REPEATABLE_READ вообще не допускает одновременного доступа к строке. Таким образом, потерянное обновление не может произойти.
Вот как установить изоляция уровень для метода:
@Transactional(isolation = Isolation.REPEATABLE_READ) public void log(String message){ // ... }
REPEATABLE_READ является уровень по умолчанию в Mysql. Oracle не поддерживает REPEATABLE_READ .
4.5. СЕРИАЛИЗИРУЕМАЯ изоляция
СЕРИАЛИЗИРУЕМЫЕ является наивысшим уровнем изоляции. Это предотвращает все упомянутые побочные эффекты concurrency, но может привести к самой низкой скорости одновременного доступа, поскольку он выполняет параллельные вызовы последовательно.
Другими словами, одновременное выполнение группы серийных транзакций имеет тот же результат, что и их выполнение в последовательном.
Теперь давайте посмотрим, как установить СЕРИАЛИЗИРУЕМЫЕ в качестве изоляция уровень:
@Transactional(isolation = Isolation.SERIALIZABLE) public void log(String message){ // ... }
5. Заключение
В этом учебнике мы исследовали распространение собственности @Transaction обстоятельно. После этого мы узнали о побочных эффектах с эквивалентности и уровнях изоляции.
Как всегда, вы можете найти полный код более на GitHub .