Де слід розмістити анотацію @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!

(Наголос додано до першого речення, інші наголоси в оригіналі.)


Великий +1 BTW, я дозволив собі зробити цитату цитатою, щоб люди не розгубилися, і додав курсив та інше з оригіналу.
TJ Crowder,

Я вже читав це твердження у Spring Docu, але досі не можу зрозуміти, чому параметри транзакції не розпізнаються інфраструктурою проксі-сервера на основі класу ? Я особисто думаю, що це лише обмеження впровадження Spring, а не проблема базової проксі-інфраструктури.
dma_k

Переглядаючи джерела Spring, можна дізнатись, що JdkDynamicAopProxyпроходить усіх радників bean щодо кожного виклику методу (див. Також DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice()), який у випадку встановлення декларативної транзакції включає BeanFactoryTransactionAttributeSourceAdvisor. У свою чергу TransactionAttributeSourcePointcut#matches(), викликається, який повинен збирати інформацію про транзакції. Він передається цільовому класу, і він завжди може пройти всі інтерфейси, які реалізує цей клас. Тепер: чому це не може працювати надійно?
dma_k

2
@dma_k - середовище виконання Java надає лише прямий доступ до анотацій класів, успадкованих від суперкласів, або анотацій методів без взаємного успадкування. Тож Pointcut.matches () повинен був би фактично рекурсивно обходити всі інтерфейси, знаходити "справжній" перевизначений метод з відображенням, обробляти мостові методи, перевантаження, varargs, дженерики, проксі-сервери, і звичайно, робити це швидко . Тоді вам доведеться мати справу з успадкуванням алмазів - яка з можливо багатьох успадкованих @Transactionalанотацій застосовується? Тож, хоча це все можливо , я не звинувачую Весну. jira.springsource.org/browse/SPR-975
Пітер Девіс

9

Ви можете розмістити їх на інтерфейсі, але попередьте, що транзакції в деяких випадках можуть не відбуватися. Див. Другу підказку у розділі 10.5.6 документації Spring:

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

Я б порадив поставити їх на реалізацію саме з цієї причини.

Крім того, для мене транзакції здаються деталями реалізації, тому вони повинні знаходитися в класі реалізації. Уявіть, що у вас є реалізації обгортки для реєстрації або тестові реалізації (макети), які не потребують транзакцій.


9

Рекомендація Spring полягає в тому, щоб ви замічали конкретні реалізації замість інтерфейсу. Неправильно використовувати анотацію в інтерфейсі, просто можливо зловживати цією функцією та ненавмисно обійти вашу декларацію @Transaction.

Якщо ви позначили щось транзакційне в інтерфейсі, а потім посилаєтесь на один із його реалізаційних класів в іншому місці навесні, не зовсім очевидно, що об’єкт, який створює spring, не буде поважати анотацію @Transactional.

На практиці це виглядає приблизно так:

public class MyClass implements MyInterface { 

    private int x;

    public void doSomethingNonTx() {}

    @Transactional
    public void toSomethingTx() {}

}

1

Підтримка @Transactional щодо конкретних класів:

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

Для розміщення @Transactional потрібні бібліотеки Spring у розділі API, що IMHO не є ефективним. Тому я вважаю за краще додавати його до реалізації, де виконується транзакція.


0

Помістити його на інтерфейс - це добре, поки всі передбачувані виконавці вашого IFC піклуються про дані TX (транзакції не є проблемами, з якими мають справу лише бази даних). Якщо метод не турбується про TX (але вам потрібно помістити його там для Hibernate чи чогось іншого), поставте його на імпл

Крім того, може бути трохи краще розмістити @Transactionalна методах в інтерфейсі:

public interface FooService {
    @Transactional(readOnly = true)
    void doSmth();
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.