В чем разница между определением @Transactional в классе и методе (Spring)
В сети нашел информацию,что отличие места установки аннотации @Transactional в том, что:
Когда транзакция установлена над классом, если возникает исключение, тогда работает rollback. Если аннотация установлена над методом, то rollback – не работает.
Когда аннотация применяется на уровне класса (class-level), тогда транзакция будет применяться для всех публичных методов данного класса, над которым нет данной транзакции (то есть достаточно транзакции над классом.) Но это работает только для методов с модификатором public. Методы с модификаторами – private, default-package и protected, будут проигнорированы.
Каждый объект сервисного класса, оборачивается в прокси. Данный режим включен по умолчанию
Аннотация @Transactional установленная над классом, будет применяться к каждому методу в этом классе, кроме метода с модификатором private.
Но если в этом классе над каким-то методом будет установлена своя аннотация @Transactional, то такая аннотация для данного метода, будет иметь приоритет над аннотацией установленной над классом.
Однако, если данный метод будет вызван из внешнего класса, то приоритет будет иметь аннотация установленная над классом .
Справедливы ли эти утверждения (применительно к версии Spring 5.x) ?
Так ли это и есть ли еще отличия ?
Ответы (1 шт):
Если есть какие-то вопросы, то всегда можно обратиться к официальной документации:
At the class level, this annotation applies as a default to all methods of the declaring class and its subclasses. Note that it does not apply to ancestor classes up the class hierarchy; methods need to be locally redeclared in order to participate in a subclass-level annotation.
Говоря по-русски аннотация над классом - это поведение транзакций, которое будет применено в методах этого класса и его наследников по умолчанию.
В приведенной в вопросе цитате частично недостоверная информация.
Однако, рядом лежит ещё одна тема. Это механизм, благодаря которому появляется "магия" транзакций.
Для реализации этой магии используется заворачивание исходного java-объекта в другой java-объект, с таким же внешним API. Это как раз и есть proxy. Важно понимать, что этот proxy принадлежит к динамически-сгенерированному классу, т.е. классу сделанному из байт-кода, который был создан во время исполнения программы, а не её компиляции.
Это самое "заворачивание исходного java-объекта в другой java-объект, с таким же внешним API" возможно реализовать двумя путями:
- Создать класс-наследник от исходного класса. Привет cglib;
- Создать класс, который реализует все интерфейсы исходного класса. Привет родной механизм из JVM.
Поскольку private (и static тоже) методы не могут быть переопределёнными/реализованными из класса-родителя/интерфейса, то простановка над ними аннотаций такого рода вообще не даст никакой пользы во всех случаях. Говоря научным языком, "магия" может появится только в местах, где присутствует виртуальный вызов. Виртуальный вызов - это вызов в процессе которого определяется в контексте какого объекта (тот самый this) он будет сделан.
Допустим у нас есть:
interface Api {
void call();
void anotherCall();
void toPrivateCall();
}
@Component
class ApiImpl implements Api {
@Transactional
void call() {
// (i1)
}
void anotherCall() {
// (i2)
this.call();
}
void toPrivateCall() {
// (i3)
this.privateCall();
}
@Transactional
private void privateCall(){
// (i4)
}
}
И вызовы
@Component
class Caller{
@Autowired
Api api;
void test(){
api.call(); //(c1)
api.anotherCall(); //(c2)
api.toPrivateCall(); //(c3)
}
}
Результат по цепочкам вызова в зависимости от технологии:
| Цепочка | Транзакция для proxy через класс-наследник | Транзакция для proxy через интерфейсы |
|---|---|---|
| с1 -> i1 | появится между с1 -> i1 | появится между с1 -> i1 |
| с2 -> i2 -> i1 | появится между i2 -> i1 | не появится |
| с3 -> i3 -> i4 | не появится | не появится |