Spring @Transactional - изоляция, распространение - PullRequest
377 голосов
/ 13 декабря 2011

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

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

Ответы [ 10 ]

382 голосов
/ 13 декабря 2011

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

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

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

  • 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() был НЕ откат, поскольку он создал свою собственную суб-транзакцию.

  • Required: мы ожидаем, что все было откатано, а резервное хранилище не изменилось.

254 голосов
/ 22 апреля 2013

PROPAGATION_REQUIRED = 0 ;Если DataSourceTransactionObject T1 уже запущен для метода M1. Если для другого метода M2 требуется объект транзакции, новый объект транзакции не создается. Один и тот же объект 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.Если одновременно выполняется другой метод M2, то M2 не должен выполняться в контексте транзакции.T1 приостанавливается до завершения M2.

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

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

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

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

Scenario2 .Если транзакция T1 считывает данные из таблицы A1.Если другая параллельная транзакция (T2) обновляет данные в таблице A1.Затем данные, которые прочитал T1, отличаются от таблицы A1. Потому что T2 обновил данные в таблице A1.Eg, если T1 прочитал= 1 и T2 обновлены a = 2. Затем a! = B.

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

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

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

Сценарий 3 называется Фантомные чтения.

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

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

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

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

}

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

95 голосов
/ 23 апреля 2013

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

Предположим, что вы отвечаете за реализацию службы регистрации , в которой пользователю отправляется электронное письмо с подтверждением. Вы получаете два сервисных объекта: один для регистрации пользователя и один для отправки сообщений электронной почты, которые последний вызывается внутри первого. Например что-то вроде этого:
/* 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 и, следовательно, возможный доступ к БД, мы должны заверить, что вызов был сделан из одной из наших служб, подразумевая, что должна существовать живая транзакция; в противном случае возникает исключение. Поэтому распространение имеет тип ОБЯЗАТЕЛЬНО .

49 голосов
/ 04 декабря 2015

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

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

}

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

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

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

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

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

Поведение Spring REQUIRED означает, что та же транзакция будет использоваться, если в текущем контексте выполнения метода бина уже есть открытая транзакция.

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

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

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

В поведении НИКОГДА не говорится, что существующая открытая транзакция еще не должна существовать. Если транзакция существует, контейнер будет выдавать исключение.

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

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

19 голосов
/ 13 декабря 2011

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

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

13 голосов
/ 26 апреля 2013

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

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

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

.

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

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

Справка:

11 голосов
/ 06 апреля 2018

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

Весной 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 вида основных проблем:

  • Грязное чтение : считывает незафиксированные данные из другогоTX (транзакция).
  • Неповторяемые чтения : считывает зафиксированные 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       |
+---------------------------+----------------+----------------------+----------------+

для примеров

7 голосов
/ 26 августа 2015

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

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

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

    @Transactional
    @Override
    public void outerMethod() {
        customerProfileDAO.method_1();
        iWorkflowDetailDao.method_2();
    }
    
  • Метод_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()) {
            }
        }
    }
    
  • Метод_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 - Propagation.MANDATORY) -
      • method_2 - только аннотации транзакций
      • Вывод: method_1 выдаст исключение, что ни одна существующая транзакция
      • externalMethod - без транзакции
      • method_1 - только аннотации транзакции
      • method_2 - Propagation.MANDATORY)
      • Вывод: method_2 будетвыбросить исключение, что никакая существующая транзакция
      • Вывод: method_1 сохранит запись в базе данных.
      • externalMethod - с транзакцией
      • method_1- Только аннотации транзакций
      • method_2 - Распространение. ОБЯЗАТЕЛЬНО)
      • Вывод: method_2 сохранит запись в базе данных.
      • Вывод: method_1 сохранит запись в базе данных.- Здесь Main Outer существующая транзакция, используемая для метода 1 и 2
      • externalMethod - С транзакцией
      • method_1 - Propagation.MANDATORY) -
      • method_2 - только аннотации транзакций и выдает исключение
      • Вывод: запись в базе данных не сохраняется, что означает откат.
      • outerMethod -С транзакцией
      • method_1 - Propagation.REQUIRES_NEW)
      • method_2 - Propagation.REQUIRES_NEW) и выдает исключение 1/0
      • Вывод: method_2 вызовет исключение, поэтому запись method_2 не будет сохранена.
      • Вывод: method_1 сохранит запись в базе данных.
      • Вывод: Откат для method_1
отсутствует
3 голосов
/ 04 декабря 2015

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

@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
    }
}
1 голос
/ 04 декабря 2015

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

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