Работает ли атрибут Spring @Transactional с закрытым методом?

196

Если у меня есть аннотация @Transactional для закрытого метода в бине Spring, оказывает ли аннотация какое-либо влияние?

Если @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).

@ См. Spring Reference: Глава 9.6 9.6 Механизмы прокси

ИМХО, вы должны использовать режим aspectJ вместо Spring Proxies, который решит проблему. А транзакционные аспекты AspectJ вплетены даже в приватные методы (проверено на Spring 3.0).

Ральф
источник
4
Обе точки не обязательно верны. Первое неверно - частные методы могут вызываться рефлексивно, но логика обнаружения прокси предпочитает не делать этого. Второй пункт верен только для прокси JDK на основе интерфейса, но не для прокси на основе подклассов CGLIB.
Скаффман
@skaffman: 1 - я уточняю свою статистику, 2. Но прокси по умолчанию основывается на интерфейсе - не так ли?
Ральф
2
Это зависит от того, использует ли цель интерфейсы или нет. Если это не так, используется CGLIB.
Скаффман
canu скажи мне резон или какую-то ссылку, почему cglib не может, а aspectj может?
Фил
1
Ссылка из ссылки в блоке ответов, если вы хотите использовать Spring Proxies [окружение по умолчанию], поместите аннотацию в doStuff () и вызовите doPrivateStuff () используя ((Bean) AopContext.currentProxy ()). DoPrivateStuff (); Он будет выполнять оба метода в одной и той же транзакции, если распространение возобновлено [среда по умолчанию].
Майкл Оуян
219

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

Это задокументировано в руководстве по весне, глава 10.5.6 :

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

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

skaffman
источник
вы уверены в этом? Я не ожидал бы, что это будет иметь значение.
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();

Spring Reference: Использование @Transactional

Примечание. В режиме прокси (который используется по умолчанию) будут перехватываться только вызовы внешних методов, поступающие через прокси. Это означает, что «самовывоз», то есть метод в целевом объекте, вызывающий какой-либо другой метод целевого объекта, не приведет к реальной транзакции во время выполнения, даже если вызываемый метод помечен знаком @Transactional!

Подумайте об использовании режима AspectJ (см. Ниже), если вы ожидаете, что самообращения будут также обернуты транзакциями. В этом случае, во-первых, не будет прокси; вместо этого целевой класс будет «соткан» (т. е. его байтовый код будет изменен), чтобы превратить его @Transactionalв поведение во время выполнения для любого метода.

Юха Сыряля
источник
Вы имеете в виду bean = new Bean ();?
willcodejavaforfood
Нет. Если я создаю bean-компоненты с новым Bean (), аннотация никогда не будет работать по крайней мере без использования Aspect-J.
Юха Сыряля
2
Спасибо! Это объясняет странное поведение, которое я наблюдал. Это противоречит интуитивному ограничению вызова внутренних методов ...
Мануэль Алдана
Я узнал, что «только внешние вызовы методов, поступающие через прокси, будут перехвачены»,
спрашивает
13

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

Я решил использовать ткачество во время компиляции, а не ткачество во время загрузки, потому что я думаю, что это в целом лучший вариант. Кроме того, я использую Java 8, поэтому вам может потребоваться настроить некоторые параметры.

Сначала добавьте зависимость для aspectjrt.

<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

Если вам нужно обернуть закрытый метод внутри транзакции и не хотите использовать aspectj, вы можете использовать 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(){
        //...
    }

}
loonis
источник
Хорошо, чтобы показать TransactionTemplateиспользование, но, пожалуйста, вызовите этот второй метод, ..RequiresTransactionа не ..InTransaction. Всегда называйте материал так, как вы хотели бы прочитать его год спустя. Также я бы поспорил, если подумать, действительно ли он требует второго частного метода: либо поместить его содержимое непосредственно в анонимную executeреализацию, либо, если это станет грязным, это может указывать на разделение реализации на другой сервис, который вы затем сможете аннотировать @Transactional.
Застрял
@ Stuck, 2-й метод действительно не нужен, но он отвечает на оригинальный вопрос о том, как применить весеннюю транзакцию к частному методу
loonis
да, я уже проголосовал за ответ, но хотел поделиться некоторым контекстом и мыслями о том, как его применить, потому что я думаю, что с точки зрения архитектуры эта ситуация является потенциальным признаком недостатка проекта.
Застрял
5

Весенние Документы объясняют это

В режиме прокси (который используется по умолчанию) перехватываются только внешние вызовы методов, поступающие через прокси. Это означает, что самовывоз, по сути, метод в целевом объекте, вызывающий другой метод целевого объекта, не приведет к фактической транзакции во время выполнения, даже если вызванный метод помечен @Transactional.

Подумайте об использовании режима AspectJ (см. Атрибут mode в таблице ниже), если вы ожидаете, что самовыводы также будут обернуты транзакциями. В этом случае, во-первых, не будет прокси; вместо этого целевой класс будет соткан (то есть его байт-код будет изменен), чтобы превратить @Transactional в поведение во время выполнения любого метода.

Другим способом является пользователь BeanSelfAware

user536161
источник
не могли бы вы добавить ссылку на BeanSelfAware? Это не похоже на класс Spring
спрашивает
@asgs Предположим, речь идет о самоинъекции (предоставьте бину экземпляр самого себя, завернутый в прокси-сервер). Вы можете увидеть примеры в stackoverflow.com/q/3423972/355438 .
Lu55
3

Ответ - нет. Пожалуйста, смотрите Spring Reference: Использование @Transactional :

@TransactionalАннотаций может быть помещен перед определением интерфейса, метод на интерфейс, определение класса, или общественный способ по классу

плющ
источник
1

Так же, как @loonis предложил использовать TransactionTemplate, можно использовать этот вспомогательный компонент (Kotlin):

@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использовать повторно существующую транзакцию или нет, но этот код определенно делает.

Lu55
источник