Spring @Transactional - изоляция, распространение

Кто-нибудь может объяснить, для чего используются параметры изоляции и распространения в аннотации @Transactional на @Transactional реальной жизни?

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

+373
источник поделиться
10 ответов

Хороший вопрос, хотя и не тривиальный.

распространения

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

  • Required: код всегда будет выполняться в транзакции. Создает новую транзакцию или использует ее повторно, если она доступна.
  • Requires_new: код всегда будет выполняться в новой транзакции. Приостанавливает текущую транзакцию, если она существует.

изоляция

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

  • Read Uncommitted: разрешает грязное чтение.
  • Read Committed: не допускает грязного чтения.
  • Repeatable Read: если строка читается дважды в одной и той же транзакции, результат всегда будет одинаковым.
  • Serializable: выполняет все транзакции в последовательности.

Различные уровни имеют разные характеристики производительности в многопоточном приложении. Я думаю, что если вы понимаете концепцию dirty reads вы сможете выбрать хороший вариант.


Пример, когда может произойти грязное чтение:

  thread 1   thread 2      
      |         |
    write(x)    |
      |         |
      |        read(x)
      |         |
    rollback    |
      v         v 
           value (x) is now dirty (incorrect)

Таким образом, разумным значением по умолчанию (если таковое может быть заявлено) может быть Read Committed, который позволяет только читать значения, которые уже были зафиксированы другими запущенными транзакциями, в сочетании с уровнем распространения Required. Тогда вы можете работать оттуда, если у вашего приложения есть другие потребности.


Практический пример того, как новая транзакция всегда будет создаваться при входе в подпрограмму provideService и завершаться при выходе:

public class FooService {
    private Repository repo1;
    private Repository repo2;

    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public void provideService() {
        repo1.retrieveFoo();
        repo2.retrieveFoo();
    }
}

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


Мы можем легко проверить поведение с помощью теста и увидеть, как результаты отличаются с уровнями распространения:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:/fooService.xml")
public class FooServiceTests {

    private @Autowired TransactionManager transactionManager;
    private @Autowired FooService fooService;

    @Test
    public void testProvideService() {
        TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
        fooService.provideService();
        transactionManager.rollback(status);
        // assert repository values are unchanged ... 
}

С уровнем распространения

  • Requires new: мы ожидаем, что fooService.provideService() НЕ был fooService.provideService() так как он создал свою собственную суб-транзакцию.

  • Required: мы ожидаем, что все было откатано, а резервный магазин не изменился.

+374
источник

PROPAGATION_REQUIRED = 0; Если DataSourceTransactionObject T1 уже запущен для метода M1.If для другого объекта M2 Transaction требуется, новый объект Transaction не создается. Тег T1 используется для M2

PROPAGATION_MANDATORY = 2; метод должен выполняться внутри транзакции. Если нет существующая транзакция выполняется, исключение будет выбрано

PROPAGATION_REQUIRES_NEW = 3; Если DataSourceTransactionObject T1 уже запущен для метода M1 и он выполняется (выполняется метод M1). Если запускается другой метод M2, тогда T1 приостанавливается на время метода M2 с новым DataSourceTransactionObject T2 для M2.M2, выполняемым в рамках собственного контекста транзакции

PROPAGATION_NOT_SUPPORTED = 4; Если DataSourceTransactionObject T1 уже запущен для метода M1.If другой метод M2 запускается одновременно. Затем M2 не должен запускаться в контексте транзакции. T1 приостанавливается до завершения работы M2.

PROPAGATION_NEVER = 5; Ни один из методов не выполняется в контексте транзакции.

Уровень изоляции: Речь идет о том, на какую транзакцию может влиять деятельность других параллельных transaction.It поддерживает согласованность, оставляя данные во многих таблицах в согласованном состоянии. Он включает блокировку строк и/или таблиц в базе данных.

Проблема с несколькими транзакциями

Сценарий 1. Если транзакция T1 считывает данные из таблицы A1, которые были написаны другой параллельной транзакцией T2.If на пути отката T2 данные, полученные посредством T1 недействителен. Eg a = 2 является исходными данными. Если T1 прочитал a = 1, который был написан T2.If откат T2, тогда a = 1 будет откатом к a = 2 в DB. Но теперь T1 имеет a = 1, но в БД таблица изменяется на a = 2.

Сценарий2. Если транзакция T1 считывает данные из таблицы A1.If другие данные обновления параллельной транзакции (T2) в таблице A1.Then данные, которые T1 прочитал, отличная от таблицы A1.Because T2 обновил данные в таблице A1.E.g, если T1 прочитал a = 1 и T2 обновлено a = 2. Затем a!= b.

Сценарий 3. Если транзакция T1 считывает данные из таблицы A1 с определенным количеством строк. Если другая параллельная транзакция (T2) вставляет больше строк в таблицу A1.The количество строк, считанных T1, отличается от строк в таблице A1

Сценарий 1 называется Грязный читает.

Сценарий 2 называется Непрерывными чтениями.

Сценарий 3 называется Phantom.

Таким образом, уровень изоляции - это расширение, на которое можно предотвратить Сценарий 1, Сценарий 2, Сценарий 3. Вы можете получить полный уровень изоляции, выполнив блокировку. Это предотвращает одновременное чтение и пишет на одни и те же данные, но это влияет на производительность. Уровень изоляции зависит от приложения к приложению, насколько изоляция требуется.

ISOLATION_READ_UNCOMMITTED: позволяет читать изменения, которые еще не были зафиксированы. Он страдает от сценария 1, сценария 2, сценария 3

ISOLATION_READ_COMMITTED. Позволяет читать данные из параллельных транзакций, которые были совершены. Он может страдать от сценария 2 и сценария 3. Поскольку другие транзакции могут обновлять данные.

ИЗОЛЯЦИЯ_REPEATABLE_READ. Несколько просмотров одного и того же поля будут давать те же результаты, пока они не будут изменены сами по себе. Это может страдать от сценария 3. Причина другие транзакции могут вставлять данные

ISOLATION_SERIALIZABLE: сценарий 1, сценарий 2, сценарий 3 никогда не бывает. Это полная изоляция. Он включает полную блокировку. замок.

Вы можете протестировать, используя

public class TransactionBehaviour {
   // set is either using xml Or annotation
    DataSourceTransactionManager manager=new DataSourceTransactionManager();
    SimpleTransactionStatus status=new SimpleTransactionStatus();
   ;


    public void beginTransaction()
    {
        DefaultTransactionDefinition Def = new DefaultTransactionDefinition();
        // overwrite default PROPAGATION_REQUIRED and ISOLATION_DEFAULT
        // set is either using xml Or annotation
        manager.setPropagationBehavior(XX);
        manager.setIsolationLevelName(XX);

        status = manager.getTransaction(Def);

    }

    public void commitTransaction()
    {


            if(status.isCompleted()){
                manager.commit(status);
        } 
    }

    public void rollbackTransaction()
    {

            if(!status.isCompleted()){
                manager.rollback(status);
        }
    }
    Main method{
        beginTransaction()
        M1();
        If error(){
            rollbackTransaction()
        }
         commitTransaction();
    }

}

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

+250
источник
другие ответы

Связанные вопросы


Похожие вопросы

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

Предположим, что вы отвечаете за внедрение службы регистрации, в которой пользователю отправляется электронное письмо с подтверждением. Вы получаете два служебных объекта: один для регистрации пользователя и один для отправки электронной почты, который последний вызывается внутри первого. Например, что-то вроде этого:
/* Sign Up service */
@Service
@Transactional(Propagation=REQUIRED)
class SignUpService{
 ...
 void SignUp(User user){
    ...
    emailService.sendMail(User);
 }
}

/* E-Mail Service */
@Service
@Transactional(Propagation=REQUIRES_NEW)
class EmailService{
 ...
 void sendMail(User user){
  try{
     ... // Trying to send the e-mail
  }catch( Exception)
 }
}

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

Вернемся к нашему примеру, на этот раз вас беспокоит безопасность базы данных, поэтому вы определяете свои классы DAO следующим образом:
/* User DAO */
@Transactional(Propagation=MANDATORY)
class UserDAO{
 // some CRUD methods
}

Считая, что всякий раз, когда создается объект DAO и, следовательно, потенциальный доступ к db, мы должны заверить, что вызов был сделан из одной из наших служб, подразумевая, что должна существовать прямая транзакция; в противном случае возникает исключение. Поэтому распространение имеет тип ОБЯЗАТЕЛЬНО.

+94
источник

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

@Transactional(isolation=Isolation.READ_COMMITTED)
public void someTransactionalMethod(Object obj) {

}

Уровень изоляции READ_UNCOMMITTED указывает, что транзакция может считывать данные, которые еще не удалены другими транзакциями.

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

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

SERIALIZABLE уровень изоляции является самым ограничивающим из всех уровней изоляции. Транзакции выполняются с блокировкой на всех уровнях (чтение, диапазон и блокировка записи), поэтому они выглядят так, как если бы они выполнялись сериализованным способом.

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

Spring ТРЕБУЕМОЕ поведение означает, что одна и та же транзакция будет использоваться, если в текущем контексте выполнения bean есть уже открытая транзакция.

Поведение

REQUIRES_NEW означает, что контейнер будет создавать новую физическую транзакцию.

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

В поведении MANDATORY указано, что существующая открытая транзакция уже должна существовать. Если исключение не будет выбрано контейнером.

В действии NEVER указано, что существующая открытая транзакция еще не существует. Если транзакция существует, исключение будет выбрано контейнером.

Поведение NOT_SUPPORTED будет выполняться вне сферы действия любой транзакции. Если открытая транзакция уже существует, она будет приостановлена.

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

+46
источник

Вы почти никогда не хотите использовать Read Uncommited, поскольку он не соответствует ACID. Read Commmited является хорошим начальным местом по умолчанию. Repeatable Read, вероятно, требуется только для сценариев создания отчетов, сворачивания или агрегации. Обратите внимание, что многие DB, включенные postgres фактически не поддерживают Repeatable Read, вместо этого вы должны использовать Serializable. Serializable полезен для вещей, которые, как вы знаете, должны произойти полностью независимо от чего-либо еще; подумайте об этом, как synchronized в Java. Serializable идет рука об руку с распространением REQUIRES_NEW.

Я использую REQUIRES для всех функций, которые запускают запросы UPDATE или DELETE, а также функции уровня обслуживания. Для функций уровня DAO, которые запускают только SELECT, я использую SUPPORTS, который будет участвовать в TX, если он уже запущен (т.е. Вызывается из сервисной функции).

+19
источник

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

Изоляция транзакций

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

.

Распространение транзакций

В корпоративном приложении для любого заданного запроса/обработки есть много компонентов, которые задействованы для выполнения работы. Некоторые из этих компонентов отмечают границы (начало/конец) транзакции, которые будут использоваться в соответствующем компоненте и его субкомпонентах. Для этой транзакционной границы компонентов Transaction Propogation указывает, будет ли соответствующий компонент участвовать или не участвовать в транзакции, и что произойдет, если вызывающий компонент уже имеет или не имеет уже созданную/запущенную транзакцию. Это то же самое, что и атрибуты транзакций Java EE. Обычно это выполняется диспетчером транзакций/соединений клиента.

Справка:

+12
источник

Транзакция представляет собой единицу работы с базой данных.

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

@Autowired
private TestDAO testDAO;

@Transactional(propagation=TransactionDefinition.PROPAGATION_REQUIRED,isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED)
public void someTransactionalMethod(User user) {

  // Interact with testDAO

}

Распространение (воспроизведение): используется для межтранзакционных отношений. (аналогично межпотоковой коммуникации Java)

+-------+---------------------------+------------------------------------------------------------------------------------------------------+
| value |        Propagation        |                                             Description                                              |
+-------+---------------------------+------------------------------------------------------------------------------------------------------+
|    -1 | TIMEOUT_DEFAULT           | Use the default timeout of the underlying transaction system, or none if timeouts are not supported. |
|     0 | PROPAGATION_REQUIRED      | Support a current transaction; create a new one if none exists.                                      |
|     1 | PROPAGATION_SUPPORTS      | Support a current transaction; execute non-transactionally if none exists.                           |
|     2 | PROPAGATION_MANDATORY     | Support a current transaction; throw an exception if no current transaction exists.                  |
|     3 | PROPAGATION_REQUIRES_NEW  | Create a new transaction, suspending the current transaction if one exists.                          |
|     4 | PROPAGATION_NOT_SUPPORTED | Do not support a current transaction; rather always execute non-transactionally.                     |
|     5 | PROPAGATION_NEVER         | Do not support a current transaction; throw an exception if a current transaction exists.            |
|     6 | PROPAGATION_NESTED        | Execute within a nested transaction if a current transaction exists.                                 |
+-------+---------------------------+------------------------------------------------------------------------------------------------------+

Изоляция. Изоляция является одним из свойств ACID (атомарность, согласованность, изоляция, долговечность) транзакций базы данных. Изоляция определяет, как целостность транзакции видна другим пользователям и системам. Он использует для блокировки ресурсов, то есть управления параллелизмом, убедитесь, что только одна транзакция может получить доступ к ресурсу в данной точке.

Восприятие блокировки: уровень изоляции определяет продолжительность удержания блокировки.

+---------------------------+-------------------+-------------+-------------+------------------------+
| Isolation Level Mode      |  Read             |   Insert    |   Update    |       Lock Scope       |
+---------------------------+-------------------+-------------+-------------+------------------------+
| READ_UNCOMMITTED          |  uncommitted data | Allowed     | Allowed     | No Lock                |
| READ_COMMITTED (Default)  |   committed data  | Allowed     | Allowed     | Lock on Committed data |
| REPEATABLE_READ           |   committed data  | Allowed     | Not Allowed | Lock on block of table |
| SERIALIZABLE              |   committed data  | Not Allowed | Not Allowed | Lock on full table     |
+---------------------------+-------------------+-------------+-------------+------------------------+

Читайте восприятие: возникают следующие 3 вида основных проблем:

  • Грязные чтения: читает незафиксированные данные из другой передачи (транзакции).
  • Неповторяемые операции чтения: чтение UPDATES с другого tx.
  • Призрак читает: читает совершенные INSERTS и/или DELETES с другого TX

Уровни изоляции с различными типами чтения:

+---------------------------+----------------+----------------------+----------------+
| Isolation Level Mode      |  Dirty reads   | Non-repeatable reads | Phantoms reads |
+---------------------------+----------------+----------------------+----------------+
| READ_UNCOMMITTED          | allows         | allows               | allows         |
| READ_COMMITTED (Default)  | prevents       | allows               | allows         |
| REPEATABLE_READ           | prevents       | prevents             | allows         |
| SERIALIZABLE              | prevents       | prevents             | prevents       |
+---------------------------+----------------+----------------------+----------------+

Например

+8
источник

Я запускал outerMethod, method_1 и method_2 с различным режимом распространения.

Ниже представлен выход для разного режима распространения.

  • Внешний метод

    @Transactional
    @Override
    public void outerMethod() {
        customerProfileDAO.method_1();
        iWorkflowDetailDao.method_2();
    }
    
  • method_1

    @Transactional(propagation=Propagation.MANDATORY)
    public void method_1() {
        Session session = null;
        try {
            session = getSession();
            Temp entity = new Temp(0l, "XXX");
            session.save(entity);
            System.out.println("Method - 1 Id "+entity.getId());
        } finally {
            if (session != null && session.isOpen()) {
            }
        }
    }
    
  • Method_2

    @Transactional()
    @Override
    public void method_2() {
        Session session = null;
        try {
            session = getSession();
            Temp entity = new Temp(0l, "CCC");
            session.save(entity);
            int i = 1/0;
            System.out.println("Method - 2 Id "+entity.getId());
        } finally {
            if (session != null && session.isOpen()) {
            }
        }
    }
    
      • outerMethod - без транзакции
      • method_1 - Распространение .MANDATORY) -
      • method_2 - только аннотация транзакции
      • Выход: метод_1 будет генерировать исключение, если никакая существующая транзакция
      • outerMethod - без транзакции
      • method_1 - Только аннотация транзакции
      • method_2 - Распространение.МАНДАТОРИЯ)
      • Вывод: метод_2 будет генерировать исключение, если никакая существующая транзакция
      • Выход: метод_1 будет сохраняться в базе данных.
      • outerMethod - с транзакцией
      • method_1 - Только аннотация транзакции
      • method_2 - Распространение.МАНДАТОРИЯ)
      • Выход: метод_2 будет сохраняться в базе данных.
      • Выход: метод_1 будет сохраняться в базе данных. - Здесь основная Внешняя существующая транзакция, используемая для обоих методов 1 и 2
      • outerMethod - с транзакцией
      • method_1 - Распространение .MANDATORY) -
      • method_2 - Только аннотация транзакции и исключение исключений
      • Выход: в базе данных не сохраняется запись.
      • outerMethod - с транзакцией
      • method_1 - Propagation.REQUIRES_NEW)
      • method_2 - Propagation.REQUIRES_NEW) и выбрасывает исключение 1/0
      • Выход: метод_2 выдает исключение, поэтому запись метода_2 не сохраняется.
      • Выход: метод_1 будет сохраняться в базе данных.
      • Выход: Откат для метода_1 отсутствует
+7
источник

Мы можем добавить для этого:

@Transactional(readOnly = true)
public class Banking_CustomerService implements CustomerService {

    public Customer getDetail(String customername) {
        // do something
    }

    // these settings have precedence for this method
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public void updateCustomer(Customer customer) {
        // do something
    }
}
+3
источник

Вы можете использовать следующее:

@Transactional(propagation = Propagation.REQUIRES_NEW)
public EventMessage<ModificaOperativitaRapporto> activate(EventMessage<ModificaOperativitaRapporto> eventMessage) {
//here some transaction related code
}

Вы также можете использовать эту вещь:

public interface TransactionStatus extends SavepointManager {
    boolean isNewTransaction();
    boolean hasSavepoint();
    void setRollbackOnly();
    boolean isRollbackOnly();
    void flush();
    boolean isCompleted();
}
+1
источник

Посмотрите другие вопросы по меткам или Задайте вопрос