Где я должен разместить аннотацию @Transactional: в определении интерфейса или в классе реализации?

91

Вопрос из заголовка в коде:

@Transactional (readonly = true)
public interface FooService {
   void doSmth ();
}


public class FooServiceImpl implements FooService {
   ...
}

против

public interface FooService {
   void doSmth ();
}

@Transactional (readonly = true)
public class FooServiceImpl implements FooService {
   ...
}
Римский
источник

Ответы:

121

Из http://static.springsource.org/spring/docs/2.0.x/reference/transaction.html

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

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

(Курсив добавлен к первому предложению, другой ударение из оригинала.)

Ромен Иппо
источник
Big +1 Кстати, я взял на себя смелость сделать цитату цитатой, чтобы люди не запутались, и добавил курсив и тому подобное из оригинала.
TJ Crowder
Я читал это утверждение раньше в документе Spring, но до сих пор не могу понять, почему параметры транзакции не распознаются инфраструктурой прокси на основе классов ? Я лично считаю, что это всего лишь ограничение реализации Spring, а не проблема базовой прокси-инфраструктуры.
dma_k
Изучив источники Spring, можно узнать, что JdkDynamicAopProxyвсе консультанты по компонентам проходят через каждый вызов метода (см. Также DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice()), который в случае настройки декларативной транзакции включает BeanFactoryTransactionAttributeSourceAdvisor. В свою очередь TransactionAttributeSourcePointcut#matches()вызывается, который должен собирать информацию о транзакции. Ему передается целевой класс, и он всегда может проходить через все интерфейсы, реализуемые этим классом. Теперь: почему это не может работать надежно?
dma_k
2
@dma_k - среда выполнения Java предоставляет прямой доступ только к аннотациям классов, унаследованных от суперклассов, или аннотациям методов без наследования вообще. Таким образом, Pointcut.matches () должен рекурсивно проходить все интерфейсы, находить «настоящий» переопределенный метод с отражением, обрабатывать методы моста, перегрузку, varargs, дженерики, прокси и, конечно же, делать это быстро . Затем вам придется иметь дело с наследованием ромба - какая из многих унаследованных @Transactionalаннотаций применима? Так что, хотя это все возможно , я не виню Spring. jira.springsource.org/browse/SPR-975
Питер Дэвис,
9

Вы можете поместить их в интерфейс, но имейте в виду, что в некоторых случаях транзакции могут не выполняться. См. Второй совет в разделе 10.5.6 документации Spring:

Spring рекомендует аннотировать только конкретные классы (и методы конкретных классов) с помощью аннотации @Transactional, а не аннотировать интерфейсы. Вы, конечно, можете разместить аннотацию @Transactional в интерфейсе (или методе интерфейса), но это работает только так, как вы ожидаете, если вы используете прокси на основе интерфейса. Тот факт, что аннотации Java не наследуются от интерфейсов, означает, что если вы используете прокси на основе классов (proxy-target-class = "true") или аспект на основе ткачества (mode = "aspectj"), то параметры транзакции не распознается инфраструктурой прокси и переплетения, и объект не будет заключен в транзакционный прокси, что было бы определенно плохим.

По этой причине я бы рекомендовал поставить их на реализацию.

Кроме того, для меня транзакции кажутся деталью реализации, поэтому они должны быть в классе реализации. Представьте, что у вас есть реализации оболочки для ведения журнала или тестирования (макеты), которые не обязательно должны быть транзакционными.

ГневКлоун
источник
9

Spring рекомендует аннотировать конкретные реализации вместо интерфейса. Использование аннотации в интерфейсе не является неправильным, просто можно злоупотребить этой функцией и непреднамеренно обойти ваше объявление @Transaction.

Если вы отметили что-то транзакционное в интерфейсе, а затем весной сослались на один из его реализующих классов в другом месте, не совсем очевидно, что объект, который создает Spring, не будет учитывать аннотацию @Transactional.

На практике это выглядит примерно так:

public class MyClass implements MyInterface { 

    private int x;

    public void doSomethingNonTx() {}

    @Transactional
    public void toSomethingTx() {}

}
ZMF
источник
1

Поддержка @Transactional в конкретных классах:

Я предпочитаю создавать решение в основном из трех частей: API, реализация и Интернет (при необходимости). Я изо всех сил стараюсь, чтобы API был как можно более легким / простым / POJO, сводя к минимуму зависимости. Это особенно важно, если вы играете в распределенной / интегрированной среде, где вам нужно много делиться API.

Для установки @Transactional требуются библиотеки Spring в разделе API, что IMHO неэффективно. Поэтому я предпочитаю добавлять его в Реализацию, в которой выполняется транзакция.

Фирдоус Амир
источник
0

Ввести его в интерфейс - это нормально, если все предполагаемые разработчики вашего IFC заботятся о данных TX (транзакции - это не проблемы, с которыми имеют дело только базы данных). Если метод не заботится о TX (но вам нужно поместить его туда для Hibernate или чего-то еще), поместите его в impl.

Кроме того, было бы лучше разместить @Transactionalметоды в интерфейсе:

public interface FooService {
    @Transactional(readOnly = true)
    void doSmth();
}
англичанин
источник