Используется ли атрибут Spring @Transactional для частного метода?

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

Если аннотация @Transactional находится в открытом методе, она работает и открывает транзакцию.

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

  }
}

...

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

Ответ 1

Вопрос не является частным или публичным, возникает вопрос: как он вызывается и какая реализация АОП вы используете?

Если вы используете (по умолчанию) Spring прокси-сервер AOP, то все функции AOP, предоставляемые Spring (например, @Transational), будут учитываться только в том случае, если вызов проходит через прокси-сервер. - Обычно это происходит, если вызываемый метод вызывается из другого bean.

Это имеет два значения:

  • Поскольку частные методы не должны вызываться из другого bean (исключение - это отражение), их аннотация @Transactional не учитывается.
  • Если этот метод является общедоступным, но он вызывается из того же bean, он не будет учитываться (этот оператор является правильным только в том случае, если используется (по умолчанию) Spring используется прокси-сервер AOP).

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

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

Ответ 2

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

Это описано в Spring Глава 10.5.6:

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

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

Ответ 3

По умолчанию атрибут @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 Справка: Использование @Transactional

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

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

Ответ 4

Да, можно использовать @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 как проверку работоспособности.

Ответ 5

Ответ - нет. См. Spring Справка: Использование @Transactional  

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

Ответ 6

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

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

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

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

Ответ 7

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

}

Ответ 8

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