Чи працює атрибут Spring @Transactional за приватним методом?


196

Якщо я маю @Transactional -анотацію приватного методу у весняному бобі, чи має анотація який-небудь ефект?

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

public class Bean {
  public void doStuff() {
     doPrivateStuff();
  }
  @Transactional
  private void doPrivateStuff() {

  }
}

...

Bean bean = (Bean)appContext.getBean("bean");
bean.doStuff();

Відповіді:


163

Питання не є приватним чи публічним, питання: як його викликати та якою реалізацією AOP ви користуєтесь!

Якщо ви використовуєте (за замовчуванням) Spring Proxy AOP, то всі функції AOP, що надаються Spring (як @Transational), будуть враховані лише в тому випадку, якщо виклик проходить через проксі. - Зазвичай це відбувається, якщо анотований метод викликається з іншого квасолі.

Це має два наслідки:

  • Оскільки приватні методи не повинні бути використані з іншого компонента (виняток - це відображення), їх @TransactionalАнотація не враховується.
  • Якщо метод є загальнодоступним, але він викликається з того самого бобу, він також не буде врахований (це твердження правильне лише у випадку, якщо (за замовчуванням) використовується Spring Proxy AOP).

@ Погляньте на весну Посилання: Розділ 9.6 9.6 Механізми передачі даних

IMHO вам слід використовувати режимJJ замість Spring Proxies, який подолає проблему. А транзакційні аспекти AspectJ вплетені навіть у приватні методи (перевірено на Spring 3.0).


4
Обидва моменти не обов'язково вірні. Перший невірний - приватні методи можна викликати рефлекторно, але логіка виявлення проксі вирішує не робити цього. Другий момент справедливий лише для проксі-серверів JDK на основі інтерфейсу, але не для проксі-серверів на основі підкласу CGLIB.
skaffman

@skaffman: 1 - я більш чітко ставлю свою статтю, 2. Але проксі за замовчуванням базується на інтерфейсі - чи не так?
Ральф

2
Це залежить від того, використовує ціль інтерфейси чи ні. Якщо цього немає, використовується CGLIB.
скафман

Чи можете мені сказати резонанс чи якусь посилання, чому cglib не може, але аспектj може?
філ

1
Якщо ви хочете використовувати Spring Proxies [середовище за замовчуванням], покладіть анотацію на doStuff () та зателефонуйте doPrivateStuff (), використовуючи ((Bean) AopContext.currentProxy ()). DoPrivateStuff (); Він виконає обидва способи в одній і тій же транзакції, якщо розповсюдження буде повторно використане [середовище за замовчуванням].
Майкл Ойян

219

Відповідь на ваше запитання - ні - @Transactionalне матиме ефекту, якщо його використовуватимуть для анотації приватних методів. Генератор проксі ігнорує їх.

Це задокументовано у розділі 10.5.6 Весняного посібника :

Видимість методу та @Transactional

Під час використання проксі-сервісів слід застосовувати @Transactionalпримітку лише до методів із відкритою видимістю. Якщо ви застосовуєте захищені, приватні або видимі для пакету методи з @Transactionalанотацією, помилка не виникає, але анотований метод не демонструє налаштованих параметрів транзакцій. Розгляньте питання про використання AspectJ (див. Нижче), якщо вам потрібно коментувати непублічні методи.


Ви впевнені в цьому? Я б не очікував, що це змінить значення.
willcodejavaforfood

як щодо того, якщо прокси-стиль - Cglib?
лілія

32

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

public class Bean {
  public void doStuff() {
    doTransactionStuff();
  }
  @Transactional
  public void doTransactionStuff() {

  }
}

Це відкриє транзакцію:

Bean bean = (Bean)appContext.getBean("bean");
bean.doTransactionStuff();

Це не буде:

Bean bean = (Bean)appContext.getBean("bean");
bean.doStuff();

Весна посилання: Використання @Transactional

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

Розгляньте можливість використання режиму AspectJ (див. Нижче), якщо ви очікуєте, що самостійні виклики також будуть завершені транзакціями. У цьому випадку проксі не буде в першу чергу; натомість цільовий клас буде "переплетений" (тобто його байтний код буде змінено), щоб перетворитись @Transactionalна поведінку під час виконання будь-якого методу.


Ви маєте на увазі bean = new Bean () ;?
willcodejavaforfood

Ні. Якщо я буду створювати боби з новим Bean (), анотація ніколи не буде працювати принаймні без використання Aspect-J.
Juha Syrjälä

2
Дякую! Це пояснює дивну поведінку, яку я спостерігав. Цілком протилежний інтуїтивно зрозумілим цим внутрішнім обмеженням виклику методу ...
manuel aldana

Я дізнався, що "єдиний зовнішній виклик методу, що надходить через проксі, буде перехоплений" важким шляхом
asgs

13

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

Я вирішив використовувати плетіння під час компіляції замість плетіння часу, оскільки я вважаю, що це загальний кращий варіант. Також я використовую Java 8, тому вам може знадобитися коригування деяких параметрів.

По-перше, додайте залежність для aspektrt.

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.8</version>
</dependency>

Потім додайте плагін AspectJ, щоб зробити власне сплетення байтових кодів у Maven (це може бути не мінімальний приклад).

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.8</version>
    <configuration>
        <complianceLevel>1.8</complianceLevel>
        <source>1.8</source>
        <target>1.8</target>
        <aspectLibraries>
            <aspectLibrary>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aspects</artifactId>
            </aspectLibrary>
        </aspectLibraries>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>compile</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Нарешті додайте це у свій клас конфігурацій

@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)

Тепер ви повинні мати можливість використовувати @Transactional на приватних методах.

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


якщо метод наближення є cglib, немає необхідності реалізувати інтерфейс, метод якого повинен бути загальнодоступним, то він може використовувати @Transactional для приватних методів?
лілія

Так, це працює за приватними методами та без інтерфейсів! Поки AspectJ налаштований належним чином, він в основному гарантує декораторів робочих методів. І користувач536161 зазначив у своїй відповіді, що це навіть працюватиме на самозвання. Це дійсно круто і просто крихітно страшно.
Джеймс Уоткінс

12

Якщо вам потрібно загорнути приватний метод всередину транзакції, і ви не хочете використовувати aspektj, ви можете використовувати TransactionTemplate .

@Service
public class MyService {

    @Autowired
    private TransactionTemplate transactionTemplate;

    private void process(){
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                processInTransaction();
            }
        });

    }

    private void processInTransaction(){
        //...
    }

}

Добре , щоб показати TransactionTemplateвикористання, але і вас просять телефонувати по телефону , що другий метод , ..RequiresTransactionа не ..InTransaction. Завжди називайте речі, як ви хотіли б їх прочитати через рік. Також я б заперечив подумати, чи дійсно потрібен другий приватний метод: або введіть його вміст безпосередньо в анонімну executeреалізацію, або якщо це стане безладним, це може бути вказівкою на поділ реалізації на інший сервіс, який ви потім можете коментувати @Transactional.
Застряг

@Stuck, 2-й метод справді не потрібен, але він відповідає на початкове запитання, як застосувати весняну транзакцію приватним методом
loonis

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

5

Spring Docs пояснюють це

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

Розгляньте можливість використання режиму AspectJ (див. Атрибут режиму в таблиці нижче), якщо ви очікуєте, що самостійні виклики також будуть завершені транзакціями. У цьому випадку проксі не буде в першу чергу; натомість цільовий клас буде сплетений (тобто його байт-код буде змінено), щоб перетворити @Transactional на поведінку під час виконання будь-якого методу.

Інший спосіб - це користувач BeanSelfAware


ви можете додати посилання на BeanSelfAware? Це не схоже на клас весни
асг

@asgs Припустимо, мова йде про самоін'єкцію (надайте бобу екземпляр себе, загорнутий у проксі). Приклади ви можете побачити в stackoverflow.com/q/3423972/355438 .
Lu55


1

Так само, як @loonis запропонував використовувати TransactionTemplate, можна використовувати цей допоміжний компонент (Котлін):

@Component
class TransactionalUtils {
    /**
     * Execute any [block] of code (even private methods)
     * as if it was effectively [Transactional]
     */
    @Transactional
    fun <R> executeAsTransactional(block: () -> R): R {
        return block()
    }
}

Використання:

@Service
class SomeService(private val transactionalUtils: TransactionalUtils) {

    fun foo() {
        transactionalUtils.executeAsTransactional { transactionalFoo() }
    }

    private fun transactionalFoo() {
        println("This method is executed within transaction")
    }
}

Не знаю, TransactionTemplateповторно використовувати існуючу транзакцію чи ні, але цей код однозначно є.

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