Атрибут Spring @Transactional работает с закрытым методом?
Если у меня есть аннотация @Transactional для закрытого метода в bean-компоненте Spring, будет ли аннотация иметь какой-либо эффект?
Если @Transactional
аннотация на публичный метод, он работает и открыть транзакцию.
public class Bean {
public void doStuff() {
doPrivateStuff();
}
@Transactional
private void doPrivateStuff() {
}
}
...
Bean bean = (Bean)appContext.getBean("bean");
bean.doStuff();
7 ответов
Ответ на ваш вопрос нет - @Transactional
не будет иметь никакого эффекта, если используется для аннотирования частных методов. Прокси-генератор будет игнорировать их.
Это задокументировано в главе 10.5.6 Руководства по Spring:
Видимость метода и
@Transactional
При использовании прокси вы должны применить
@Transactional
аннотация только к методам с публичной видимостью. Если вы аннотируете защищенные, частные или видимые в пакете методы с помощью@Transactional
аннотация, ошибка не возникает, но аннотированный метод не отображает настроенные параметры транзакции. Рассмотрите возможность использования AspectJ (см. Ниже), если вам нужно аннотировать закрытые методы.
Вопрос не является частным или публичным, вопрос в том, как он вызывается и какую реализацию AOP вы используете!
Если вы используете (по умолчанию) Spring Proxy AOP, то все функции AOP, предоставляемые Spring (например, @Transational
) будет учитываться только в том случае, если вызов проходит через прокси. - Обычно это так, если аннотированный метод вызывается из другого компонента.
Это имеет два значения:
- Поскольку частные методы не должны вызываться из другого компонента (исключение составляет отражение), их
@Transactional
Аннотация не учитывается. - Если метод является общедоступным, но вызывается из того же компонента, он также не будет учитываться (это утверждение верно только в том случае, если (по умолчанию) используется Spring Proxy AOP).
@ См. Spring Reference: Глава 9.6 9.6 Механизмы прокси
ИМХО, вы должны использовать режим aspectJ вместо Spring Proxies, который решит проблему. А транзакционные аспекты AspectJ вплетены даже в приватные методы (проверено на Spring 3.0).
По умолчанию @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
в поведение во время выполнения на любом виде метода.
Если вам нужно обернуть закрытый метод внутри транзакции и не хотите использовать 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(){
//...
}
}
Да, можно использовать @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 как проверку работоспособности
Весенние Документы объясняют это
В режиме прокси (который используется по умолчанию) перехватываются только внешние вызовы методов, поступающие через прокси. Это означает, что самовывоз, по сути, метод в целевом объекте, вызывающий другой метод целевого объекта, не приведет к реальной транзакции во время выполнения, даже если вызванный метод помечен @Transactional.
Подумайте об использовании режима AspectJ (см. Атрибут mode в таблице ниже), если вы ожидаете, что самовыводы также будут обернуты транзакциями. В этом случае, во-первых, не будет прокси; вместо этого целевой класс будет соткан (то есть его байт-код будет изменен), чтобы превратить @Transactional в поведение во время выполнения для любого метода.
Другим способом является пользователь BeanSelfAware
Ответ - нет. Пожалуйста, смотрите Spring Reference: Использование @Transactional:
@Transactional
Аннотация может быть помещена перед определением интерфейса, методом на интерфейсе, определением класса или открытым методом на классе
Так же, как @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
повторно использовать существующую транзакцию или нет, но этот код определенно делает.