Весна @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, транзакція залишалася б відкритою, якби транзакція була вже відкрита при введенні в розпорядок роботи. Зауважте також, що результат 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: ми сподіваємось, що все було відмовлено, і магазин не змінився.


Як ця остання посилання стосується того, про що ви говорите? Відповідно до пов'язаних документів, сеанс, здається, визначає, що таке поточна транзакція, а не заводська сесія.
Стипендіати доналу

@Donal, пробач, що це було не ясно. Моя думка, оскільки sessionFactory.getCurrentTransaction()було додано, що більше не потрібно запускати HibernateTemplateдля управління транзакціями. Я її зняв :)
Йохан Шьоберг

Насправді моє запитання стосувалося того, куди вказується посилання. :-)
Донецькі стипендіати

як отримати зміни, внесені в поточній транзакції- stackoverflow.com/questions/36132667/…
Прасанна Кумар, HA

304

PROPAGATION_REQUIRED = 0 ; Якщо DataSourceTransactionObject T1 вже запущений для методу M1. Якщо для іншого методу M2 не потрібен об'єкт транзакції, новий об'єкт транзакції не створюється. Ідентичний об'єкт T1 використовується для M2

PROPAGATION_MANDATORY = 2 ; метод повинен працювати в рамках транзакції. Якщо не існує жодної існуючої транзакції, буде викинуто виняток

PROPAGATION_REQUIRES_NEW = 3 ; Якщо для методу M1 вже запущено DataSourceTransactionObject T1, і він працює (метод виконання 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 read a = 1, який був записаний T2. Якщо T2 відкату, то a = 1 буде відкат до a = 2 у DB.But, Тепер, T1 має a = 1, але в таблиці DB він змінено на a = 2.

Scenario2. Якщо транзакція T1 зчитує дані з таблиці A1. Якщо інша одночасна транзакція (T2) оновить дані таблиці A1.Тоді дані, які прочитав T1, відрізняється від таблиці A1. Тому що T2 оновив дані таблиці A1.Eg, якщо T1 read a = 1 і T2 оновлено a = 2. Потім a! = b.

Сценарій 3. Якщо транзакція T1 читає дані з таблиці А1 з певною кількістю рядків. Якщо інша паралельна транзакція (T2) вставляє більше рядків у таблицю A1. Кількість рядків, прочитаних T1, відрізняється від рядків у таблиці A1

Сценарій 1 називається " Брудні читання".

Сценарій 2 називається читанням, що не повторюється.

Сценарій 3 називається Phantom reads.

Отже, рівень ізоляції - це розширення, до якого можна запобігти Сценарій 1, Сценарій 2, Сценарій 3 . Ви можете отримати повний рівень ізоляції, застосувавши блокування. Це запобігає виникненню одночасних зчитувань і запису в ті самі дані. Але це впливає на продуктивність. Рівень ізоляції залежить від додатку до програми, скільки потрібно ізоляції.

ISOLATION_READ_UNCOMMITTED : Дозволяє читати зміни, які ще не здійснені. Страждає від Сценарій 1, Сценарій 2, Сценарій 3

ISOLATION_READ_COMMITTED : Дозволяє читати з одночасних транзакцій, які були здійснені. Це може постраждати від сценарію 2 та сценарію 3. Оскільки інші транзакції можуть оновлювати дані.

ISOLATION_REPEATABLE_READ : Кілька зчитувань одного і того ж поля дають ті самі результати, поки воно не зміниться. Це може постраждати від сценарію 3. Тому що інші операції можуть вставляти дані

ІЗОЛЯЦІЯ_СЕРІАЛІЗАЦІЙНА : Сценарій 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 повинен бути виконаний у своїй власній, новій транзакції, незалежно від того, яку поведінку розповсюдження він визначає (наприклад, лише ЗАЯВЛЕНО)?
Корнел Массон

Це дійсно пізно до показу, але коли PROPAGATION_REQUIRES_NEW, що відбувається з T1 (який використовується M1), якщо з M1 стався ще один новий виклик? (скажімо M1.1)
Тім З.

115

Достатнє пояснення щодо кожного параметра дає інші відповіді; Однак ви попросили прикладу реального світу, ось який пояснює мету різних варіантів поширення :

Припустимо, ви відповідаєте за впровадження послуги реєстрації, в якій електронною поштою надсилається електронний лист для підтвердження. Ви пропонуєте два об’єкти обслуговування: один для реєстрації користувача та один для надсилання електронної пошти, який останній називається всередині першого. Наприклад щось подібне:

/* 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, нам потрібно запевнити, що дзвінок здійснювався всередині однієї з наших служб, маючи на увазі, що транзакція повинна існувати; в іншому випадку відбувається виняток. Тому поширення має тип ОБОВ'ЯЗКОВО .


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 зазначає, що якщо транзакція зчитує один запис із бази даних кілька разів, результат усіх цих операцій зчитування завжди повинен бути однаковим.

Рівень ізоляції СЕРІАЛІЗАЦІЙНИЙ є найбільш обмежуючим із усіх рівнів ізоляції. Операції виконуються з блокуванням на всіх рівнях (блокування читання, діапазону та запису), таким чином вони виглядають так, ніби вони були виконані серійно.

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

ПОВЕРНЕНА ПОТРІБНА поведінка означає, що та сама транзакція буде використовуватися, якщо вже є відкрита транзакція в поточному контексті виконання методу bean.

REQUIRES_NEW поведінка означає, що контейнер завжди буде створювати нову фізичну транзакцію.

Поведінка NESTED змушує вкладені весняні транзакції використовувати ту саму фізичну транзакцію, але встановлює точки збереження між вкладеними викликами, щоб внутрішні транзакції також могли відступати незалежно від зовнішніх транзакцій.

ОБОВ'ЯЗКОВА поведінка говорить, що існуюча відкрита транзакція повинна вже існувати. Якщо не виняток, то контейнер буде кинутий.

У поведінці НІКОЛИ не зазначено, що існуюча відкрита транзакція вже не повинна існувати. Якщо транзакція існує, виняток буде викинуто контейнером.

Поведінка NOT_SUPPORTED буде виконуватись поза межами будь-якої транзакції. Якщо відкрита транзакція вже існує, вона буде призупинена.

Поведінка SUPPORTS виконуватиметься в межах транзакції, якщо відкрита транзакція вже існує. Якщо немає вже відкритої транзакції, метод буде виконуватися в будь-якому випадку, але без транзакційним способом.


4
Якби ви могли додати, коли використовувати який із них, було б набагато корисніше.
Кумар Маніш

Наведіть кілька прикладів, Це буде дуже корисно для початківців
nitinsridar

23

Транзакція являє собою одиницю роботи з базою даних.

У весняному 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 (Atomicity, Consistency, Isolation, Durability) транзакцій із базами даних. Ізоляція визначає, наскільки цілісність транзакцій видима для інших користувачів та систем. Він використовує для блокування ресурсів, тобто контроль одночасності, переконайтеся, що лише одна транзакція може отримати доступ до ресурсу в заданій точці.

Блокування сприйняття: рівень ізоляції визначає тривалість утримання замків.

+---------------------------+-------------------+-------------+-------------+------------------------+
| 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     |
+---------------------------+-------------------+-------------+-------------+------------------------+

Сприйняття читання: виникають такі три основні проблеми:

  • Брудне зчитування : зчитує неподані дані з іншого tx (транзакції).
  • Читання, що не повторюється : читання, здійснене UPDATESз іншого tx.
  • Phantom читає : читає скоєне 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       |
+---------------------------+----------------+----------------------+----------------+

для прикладів


20

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

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


13

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

Ізоляція транзакцій

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

.

Поширення транзакцій

У корпоративній заявці на будь-який заданий запит / обробку є багато компонентів, які беруть участь у виконанні роботи. Деякі з цих компонентів позначають межі (початок / кінець) транзакції, яка буде використовуватися у відповідному компоненті та його підкомпонентах. Для цього транзакційного кордону компонентів Transaction Propogation визначає, чи буде відповідний компонент брати участь чи не буде брати участь у транзакції, і що відбувається, якщо компонент, що викликає, вже має або не має транзакції, яка вже була створена / розпочата Це те саме, що атрибути транзакцій Java EE. Зазвичай це реалізується менеджером транзакцій / з'єднань з клієнтом.

Довідка:


1
Чудово, вся інформація в одному місці, посилання дуже корисні, дякую @Gladwin Burboz
nitinsridar

7

Я бігав 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()) {
            }
        }
    }
      • externalMethod - Без транзакцій
      • метод_1 - розповсюдження. ОБОВ'ЯЗКОВО) -
      • method_2 - лише анотація транзакцій
      • Вихід: method_1 викине виняток, що немає жодної існуючої транзакції
      • externalMethod - Без транзакцій
      • method_1 - лише анотація транзакцій
      • метод_2 - розповсюдження. ОБОВ'ЯЗКОВО)
      • Вихід: method_2 викине виняток, що немає жодної існуючої транзакції
      • Вихід: method_1 збереже запис у базі даних.
      • externalMethod - З транзакцією
      • method_1 - лише анотація транзакцій
      • метод_2 - розповсюдження. ОБОВ'ЯЗКОВО)
      • Вихід: method_2 збереже запис у базі даних.
      • Вихід: method_1 збереже запис у базі даних. - Тут основна зовнішня транзакція, що використовується як для методу 1, так і для другого
      • externalMethod - З транзакцією
      • метод_1 - розповсюдження. ОБОВ'ЯЗКОВО) -
      • method_2 - лише анотація транзакцій і видає виняток
      • Вихід: у базі даних не зберігається запис, це означає, що відкат зроблено.
      • externalMethod - З транзакцією
      • метод_1 - розповсюдження.REQUIRES_NEW)
      • method_2 - розповсюдження.REQUIRES_NEW) та кидає виняток 1/0
      • Вихід: method_2 викине виняток, тому запис method_2 не зберігається.
      • Вихід: method_1 збереже запис у базі даних.
      • Вихід: Відкат для методу_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();
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.