Весна - @ Трансакційна - Що відбувається у фоновому режимі?


334

Я хочу знати, що насправді відбувається, коли ви коментуєте метод @Transactional? Звичайно, я знаю, що Весна перетворить цей метод в транзакцію.

Але у мене є такі сумніви:

  1. Я чув, що Spring створює проксі-клас ? Може хто - то пояснити це більш глибини . Що насправді знаходиться в цьому проксі-класі? Що відбувається з фактичним класом? І як я можу побачити створений Spring прокси-клас
  2. Я також читав у весняних документах, що:

Примітка: Оскільки цей механізм заснований на проксі- серверах, перехоплюватимуться лише зовнішні виклики методу, що надходять через проксі . Це означає, що "самовикликання", тобто метод всередині цільового об'єкта, що викликає якийсь інший метод цільового об'єкта, не призведе до фактичної транзакції під час виконання, навіть якщо позначений метод позначений @Transactional!

Джерело: http://static.springsource.org/spring/docs/2.0.x/reference/transaction.html

Чому під транзакцією будуть здійснюватися лише зовнішні виклики методів, а не методи самовикликання?


2
Відповідне обговорення тут: stackoverflow.com/questions/3120143 / ...
dma_k

Відповіді:


255

Це велика тема. Довідковий документ Spring присвячує йому кілька глав. Я рекомендую ознайомитись з документами, орієнтованими на аспект програмування та транзакцій , оскільки підтримка декларативних транзакцій Spring використовує AOP у своїй основі.

Але на дуже високому рівні Spring створює проксі для класів, які декларують @Transactional на самому класі або на членах. Проксі-сервер переважно невидимий під час виконання. Він забезпечує спосіб Spring спринцювати поведінку до, після або навколо викликів методу в об'єкт, що проксі. Управління транзакціями - лише один із прикладів поведінки, яку можна підключити. Інший контроль перевірок безпеки. І ви можете також надати свої речі для таких речей, як ведення журналів. Отже, коли ви коментуєте метод за допомогою @Transactional , Spring динамічно створює проксі-сервер, який реалізує той же інтерфейс (и), що і клас, який ви коментуєте . І коли клієнти здійснюють дзвінки у ваш об’єкт, виклики перехоплюються та поведінка вводиться через проксі-механізм.

До речі, операції в EJB працюють аналогічно.

Як ви помітили, через механізм проксі працює лише тоді, коли дзвінки надходять від якогось зовнішнього об'єкта. Коли ви здійснюєте внутрішній дзвінок всередині об'єкта, ви дійсно здійснюєте дзвінок через посилання " це ", яке обходить проксі. Однак існують способи подолати цю проблему. Я пояснюю один підхід у цій публікації на форумі, в якій я використовую BeanFactoryPostProcessor для введення екземпляра проксі-класу в класи "самопосилання" під час виконання. Я зберігаю цю посилання на змінну члена під назвою " я ". Тоді, якщо мені потрібно зробити внутрішні виклики, які потребують зміни стану транзакції потоку, я направляю виклик через проксі (наприклад, " me.someMethod ()".) Повідомлення на форумі пояснює більш докладно. Зауважте, що код BeanFactoryPostProcessor був би дещо іншим, як це було написано ще у часовій рамці Spring 1.x. Але, сподіваємось, це дає вам уявлення. У мене є оновлена ​​версія, Можливо, я міг би зробити доступним.


4
>> Проксі-сервер переважно невидимий під час виконання. Мені цікаво їх побачити :) Відпочинок .. ваша відповідь була дуже вичерпною. Це ти другий раз, коли ти мені допомагаєш .. Дякую за всю допомогу.
пік

17
Нема проблем. Ви можете побачити проксі-код, якщо перейти через відладчик. Це, мабуть, найпростіший спосіб. Немає ніякої магії; вони просто заняття в рамках пакетів Spring.
Роб H

І якщо метод, який має анотацію @Transaction, реалізує інтерфейс, пружина буде використовувати динамічний проксі API для введення транзакціонування, а не використання проксі. Я вважаю за краще, щоб мої трансакціонізовані класи реалізували інтерфейси в будь-якому випадку.
Майкл Вілз

1
Я також знайшов схему «я» (використовуючи явну проводку, щоб зробити це так, як мені здається), але я думаю, що якщо ви будете робити це так, вам, мабуть, краще рефакторинг, щоб не повинен. Але так, це може бути часом дуже незручно!
Дональні стипендіати

2
2019: Коли ця відповідь старіє, відповідне повідомлення на форумі вже не доступне, що описує випадок, коли вам потрібно зробити внутрішній дзвінок всередині об'єкта, не обходячи проксі, використовуючиBeanFactoryPostProcessor . Однак є (на мій погляд) дуже подібний метод, описаний у цій відповіді: stackoverflow.com/a/11277899/3667003 ... та подальші рішення також у цілому потоці.
Z3d4s

196

Коли Spring завантажує ваші визначення bean та налаштований на пошук @Transactionalанотацій, він створить ці проксі-об’єкти навколо вашого фактичного bean . Ці проксі-об'єкти - це екземпляри класів, які автоматично генеруються під час виконання. Поведінка цих проксі-об'єктів за замовчуванням, коли викликається метод, полягає лише в тому, щоб викликати той самий метод на "target" bean (тобто ваш bean).

Однак проксі-сервери також можуть постачатися з перехоплювачами, і при наявності цих перехоплювачів буде викликаний проксі-сервер, перш ніж він почне використовувати метод вашого цільового квасолі. Для цільових бобів, позначених за допомогою @Transactional, Spring створить TransactionInterceptorі передасть його до згенерованого проксі-об'єкта. Отже, коли ви викликаєте метод з коду клієнта, ви викликаєте метод на об'єкті проксі, який спочатку викликає TransactionInterceptor(який починає транзакцію), який, у свою чергу, викликає метод на вашому цільовому бобі. Коли виклик закінчується, TransactionInterceptorздійснює / повертає транзакцію. Це прозоро для клієнтського коду.

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

Чи допомагає це?


36
> Пам'ятайте, Весна загортає вашу квасолю в проксі, ваш боб не знає про це. Це все сказало. Яка чудова відповідь. Дякуємо за допомогу.
пік

Відмінне пояснення для проксі і перехоплювачів. Тепер я розумію, що весна реалізує проксі-об'єкт для перехоплення викликів до цільового боба. Дякую!
дхараг

Я думаю, що ви намагаєтесь описати цю картину весняної документації, і бачення цієї картини мені дуже допомагає: docs.spring.io/spring/docs/4.2.x/spring-framework-reference/…
WesternGun

44

Як візуальна людина, я люблю зважувати схему послідовностей проксі-шаблону. Якщо ви не знаєте, як читати стрілки, я читаю першу подібну: ClientExecutes Proxy.method().

  1. Клієнт викликає метод з цілі зі своєї точки зору і мовчки перехоплюється проксі
  2. Якщо визначений раніше аспект, проксі-сервер виконує його
  3. Потім виконується власне метод (ціль)
  4. Після повернення та після кидання - необов'язкові аспекти, які виконуються після повернення методу та / або якщо метод видає виняток
  5. Після цього проксі виконує наступний аспект (якщо він визначений)
  6. Нарешті проксі повертається до виклику клієнта

Діаграма послідовності шаблонів проксі (Мені було дозволено розмістити фото за умови, що я згадав його походження. Автор: Ноель Вейс, веб-сайт: www.noelvaes.eu)


27

Найпростіша відповідь:

За допомогою будь-якого методу ви оголошуєте @Transactionalмежу початку транзакції та межі завершення, коли метод завершується.

Якщо ви використовуєте дзвінок JPA, то всі межі транзакції містяться в цій межі транзакції .

Скажімо, ви зберігаєте ent1, entitet2 і entitet3. Тепер при збереженні entity3 виключення відбувається , то , як enitiy1 і entity2 приходить в тій же транзакції, entity1 і entity2 буде Відкат з entity3.

Угода:

  1. сутність1.сав
  2. сутність2.сав
  3. сутність3.сав

Будь-який виняток призведе до відкату всіх транзакцій JPA з DB. Внутрішньо транзакції JPA використовуються Spring.


2
"Виняток A̶n̶y̶ призведе до відкату всіх транзакцій JPA з БД." Зауважте, що лише RuntimeException призводить до відката. Перевірені винятки не призведуть до відкату.
Арджун

2

Можливо, пізно, але я наткнувся на щось, що пояснює вашу стурбованість щодо проксі (тільки "зовнішні" виклики методу, що надходять через проксі, буде приховано).

Наприклад, у вас клас, який виглядає приблизно так

@Component("mySubordinate")
public class CoreBusinessSubordinate {

    public void doSomethingBig() {
        System.out.println("I did something small");
    }

    public void doSomethingSmall(int x){
        System.out.println("I also do something small but with an int");    
  }
}

і у вас є аспект, який виглядає приблизно так:

@Component
@Aspect
public class CrossCuttingConcern {

    @Before("execution(* com.intertech.CoreBusinessSubordinate.*(..))")
    public void doCrossCutStuff(){
        System.out.println("Doing the cross cutting concern now");
    }
}

Коли ви виконаєте його так:

 @Service
public class CoreBusinessKickOff {

    @Autowired
    CoreBusinessSubordinate subordinate;

    // getter/setters

    public void kickOff() {
       System.out.println("I do something big");
       subordinate.doSomethingBig();
       subordinate.doSomethingSmall(4);
   }

}

Результати виклику kickOff вище наведеного коду вище.

I do something big
Doing the cross cutting concern now
I did something small
Doing the cross cutting concern now
I also do something small but with an int

але коли ви змінюєте код на

@Component("mySubordinate")
public class CoreBusinessSubordinate {

    public void doSomethingBig() {
        System.out.println("I did something small");
        doSomethingSmall(4);
    }

    public void doSomethingSmall(int x){
       System.out.println("I also do something small but with an int");    
   }
}


public void kickOff() {
  System.out.println("I do something big");
   subordinate.doSomethingBig();
   //subordinate.doSomethingSmall(4);
}

Розумієте, метод внутрішньо викликає інший метод, щоб він не був перехоплений, а вихід буде виглядати так:

I do something big
Doing the cross cutting concern now
I did something small
I also do something small but with an int

Ви можете обійти це, зробивши це

public void doSomethingBig() {
    System.out.println("I did something small");
    //doSomethingSmall(4);
    ((CoreBusinessSubordinate) AopContext.currentProxy()).doSomethingSmall(4);
}

Фрагменти коду, взяті з: https://www.intertech.com/Blog/secrets-of-the-spring-aop-proxy/


1

Усі існуючі відповіді вірні, але я не можу дати лише цю складну тему.

Для вичерпного практичного пояснення ви можете ознайомитись із цим посібником Spring @Transactional In-Depth , який намагається найкращим чином висвітлити управління транзакціями у ~ 4000 простих слів з великою кількістю прикладів коду.


Було чудово ...
Alpit Anand
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.