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

447

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

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

Мат Б.
источник

Ответы:

442

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

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

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

  • 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, транзакция оставалась бы открытой, если транзакция была уже открыта при входе в подпрограмму. Также обратите внимание, что результат a 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: мы ожидаем, что все было отменено, а резервный магазин не изменился.

Johan Sjöberg
источник
Как эта последняя ссылка связана с тем, о чем вы говорите? Согласно связанным документам, кажется, что сессия определяет текущую транзакцию, а не фабрику сессий.
Донал Феллоуз
@ Донал, извините, это не ясно. Моя точка зрения заключалась в том, что с момента sessionFactory.getCurrentTransaction()добавления больше не нужно запускать HibernateTemplateуправление транзакциями. Я удалил его :)
Йохан Шеберг
Мой вопрос был просто о том, куда указывает ссылка, правда. :-)
Donal Fellows
как получить изменения, внесенные в текущую транзакцию
Прасанна Кумар, HA,
304

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.Если откат T2, тогда a = 1 будет откат до a = 2 в DB. Но, теперь, T1 имеет a = 1, но в таблице DB он изменяется на a = 2.

Сценарий 2. Если транзакция T1 считывает данные из таблицы A1.Если другая параллельная транзакция (T2) обновляет данные в таблице A1.Затем данные, которые T1 прочитал, отличаются от таблицы A1. Потому что T2 обновил данные в таблице A1.Eg, если T1 прочитайте a = 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();
    }

}

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

абишкар бхаттараи
источник
как получить изменения, внесенные в текущую транзакцию- stackoverflow.com/questions/36132667/…
Прасанна Кумар, HA,
2
Каково взаимодействие между уровнем изоляции и распространением ? Если метод 1 запускает транзакцию с уровнем изоляции, скажем, READ_COMMITTED, и позже вызывает method2 с уровнем REPEATABLE_READ, то обязательно метод 2 должен быть выполнен в своей собственной новой транзакции, независимо от того, какое поведение распространения он указывает (например, только REQUIRED)?
Корнел Массон
Это действительно поздно для шоу, но когда PROPAGATION_REQUIRES_NEW, что происходит с T1 (который используется M1), если другой новый вызов произошел с M1? (скажем, M1.1)
Тим З.
116

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

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

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

ye9ane
источник
26
Прекрасный пример для REQUIRES_NEW.
Рави Таплиял
5
Хорошее объяснение! Кстати, что по умолчанию для распространения? Также было бы еще лучше, если бы вы могли привести такой пример и для изоляции. Большое спасибо.
Пракаш К
5
@PrakashK По умолчанию ТРЕБУЕТСЯ. ( docs.spring.io/spring-framework/docs/current/javadoc-api/org/… )
ihebiheb
59

Уровень изоляции определяет, как изменения, внесенные в один репозиторий данных одной транзакцией, влияют на другие одновременные параллельные транзакции, а также как и когда эти измененные данные становятся доступными для других транзакций. Когда мы определяем транзакцию с использованием среды 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 будет выполняться в области транзакции, если открытая транзакция уже существует. Если нет уже открытой транзакции, метод все равно будет выполняться, но не транзакционным способом.

REOs
источник
4
Если бы вы могли добавить, когда использовать какой из них, было бы гораздо более полезным.
Кумар Маниш
Приведите несколько примеров, это было бы очень полезно для начинающих
nitinsridar
23

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

В TransactionDefinitionSpring интерфейс, который определяет 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       |
+---------------------------+----------------+----------------------+----------------+

Например

Premraj
источник
20

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

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

AngerClown
источник
13

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

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

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

,

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

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

Ссылка:

Гладвин Бурбоз
источник
1
Отлично, вся информация в одном месте, ссылки очень полезны, спасибо @Gladwin Burboz
nitinsridar
7

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

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

@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

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

@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();
}
Анкит
источник