Czy atrybut Spring @Transactional działa na metodzie prywatnej?

196

Jeśli mam adnotację @Transactional na prywatnej metodzie w fasoli wiosennej, czy adnotacja ma jakikolwiek wpływ?

Jeśli @Transactionaladnotacja dotyczy metody publicznej, działa i otwiera transakcję.

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

  }
}

...

Bean bean = (Bean)appContext.getBean("bean");
bean.doStuff();
Juha Syrjälä
źródło

Odpowiedzi:

163

Pytanie nie jest prywatne ani publiczne, pytanie brzmi: w jaki sposób jest wywoływane i jakiej implementacji AOP używasz!

Jeśli użyjesz (domyślnie) Spring AOP AOP, wszystkie funkcje AOP zapewniane przez Spring (jak @Transational) zostaną wzięte pod uwagę tylko wtedy, gdy wywołanie przejdzie przez proxy. - Zwykle dzieje się tak, jeśli metoda z adnotacjami jest wywoływana z innej fasoli.

Ma to dwie konsekwencje:

  • Ponieważ nie można wywoływać metod prywatnych z innej fasoli (wyjątkiem jest odbicie), ich @Transactionaladnotacja nie jest brana pod uwagę.
  • Jeśli metoda jest publiczna, ale jest wywoływana z tego samego komponentu bean, również nie zostanie wzięta pod uwagę (ta instrukcja jest poprawna tylko wtedy, gdy zostanie użyta (domyślnie) AOP Spring Proxy).

@ Patrz Spring Reference: Rozdział 9.6 9.6 Mechanizmy proxy

IMHO powinieneś użyć trybu aspektJ zamiast Spring Proxy, które pokonają problem. Aspekty transakcyjne AspectJ są wplecione nawet w metody prywatne (sprawdzone dla Spring 3.0).

Ralph
źródło
4
Oba punkty niekoniecznie są prawdziwe. Pierwszy jest niepoprawny - metody prywatne można wywoływać refleksyjnie, ale logika wykrywająca proxy decyduje się tego nie robić. Drugi punkt dotyczy tylko serwerów proxy JDK opartych na interfejsie, ale nie dotyczy serwerów proxy opartych na podklasie CGLIB.
skaffman,
@skaffman: 1 - doprecyzowuję moją statystykę, 2. Ale domyślny serwer proxy jest oparty na interfejsie - prawda?
Ralph
2
To zależy od tego, czy cel używa interfejsów, czy nie. Jeśli nie, używany jest CGLIB.
skaffman,
możesz powiedzieć mi rezon lub jakieś odniesienie, dlaczego cglib nie może, ale aspekt może?
phil
1
Odwołanie z linku w bloku odpowiedzi, jeśli chcesz użyć Spring Proxies [środowisko domyślne], umieść adnotację na doStuff () i wywołaj doPrivateStuff () za pomocą ((Bean) AopContext.currentProxy ()). DoPrivateStuff (); Wykona obie metody w ramach tej samej transakcji, jeśli propagacja jest wymagana [środowisko domyślne].
Michael Ouyang
219

Odpowiedź na twoje pytanie brzmi „nie” - @Transactionalnie odniesie żadnego skutku, jeśli zostanie użyta do oznaczenia prywatnych metod. Generator proxy je zignoruje.

Jest to udokumentowane w rozdziale 10.5.6 Podręcznika Spring :

Widoczność metody i @Transactional

Korzystając z serwerów proxy, @Transactionaladnotację należy stosować tylko do metod z widocznością publiczną. Jeśli adnotujesz metody chronione, prywatne lub widoczne w pakiecie za pomocą @Transactionaladnotacji, nie zostanie zgłoszony żaden błąd, ale metoda z adnotacją nie wyświetla skonfigurowanych ustawień transakcyjnych. Rozważ użycie AspectJ (patrz poniżej), jeśli chcesz opisać metody niepubliczne.

skaffman
źródło
Jesteś tego pewien? Nie spodziewałbym się, że to coś zmieni.
willcodejavaforfood,
co powiesz, jeśli stylem proxy jest Cglib?
lily
32

Domyślnie ten @Transactionalatrybut działa tylko podczas wywoływania metody z adnotacjami w odniesieniu do odwołania uzyskanego z applicationContext.

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

  }
}

Spowoduje to otwarcie transakcji:

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

To nie będzie:

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

Odniesienie wiosny: Korzystanie z @Transactional

Uwaga: W trybie proxy (który jest domyślny) przechwytywane będą tylko wywołania metody „zewnętrznej” przychodzące przez proxy. Oznacza to, że „samodzielne wywołanie”, tj. Metoda w obiekcie docelowym wywołująca inną metodę obiektu docelowego, nie doprowadzi do faktycznej transakcji w czasie wykonywania, nawet jeśli wywoływana metoda jest oznaczona @Transactional!

Zastanów się nad użyciem trybu AspectJ (patrz poniżej), jeśli oczekujesz, że auto-wywołania będą również pakowane w transakcje. W takim przypadku nie będzie serwera proxy; zamiast tego klasa docelowa zostanie „utkana” (tzn. jej kod bajtu zostanie zmodyfikowany), aby zmienić się @Transactionalw zachowanie w czasie wykonywania dowolnej metody.

Juha Syrjälä
źródło
Czy masz na myśli bean = new Bean () ;?
willcodejavaforfood,
Nie. Jeśli utworzę fasolę za pomocą nowego komponentu Bean (), adnotacja nigdy nie będzie działać, przynajmniej bez użycia Aspect-J.
Juha Syrjälä,
2
dzięki! To wyjaśnia dziwne zachowanie, które obserwowałem. Całkiem sprzeczne z intuicją to wewnętrzne ograniczenie wywoływania metod ...
manuel aldana
Nauczyłem się, że „tylko zewnętrzne wywołania metod przychodzące przez proxy będą przechwytywane” w trudny sposób
asgs 18.10.18
13

Tak, można używać @Transactional na prywatnych metodach, ale jak wspomnieli inni, nie będzie to działać od razu po wyjęciu z pudełka. Musisz użyć AspectJ. Zajęło mi trochę czasu, aby dowiedzieć się, jak to działa. Podzielę się swoimi wynikami.

Wybrałem tkanie w czasie kompilacji zamiast tkania w czasie ładowania, ponieważ uważam, że jest to ogólnie lepsza opcja. Używam także Java 8, więc może być konieczne dostosowanie niektórych parametrów.

Najpierw dodaj zależność aspekjrt.

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.8</version>
</dependency>

Następnie dodaj wtyczkę AspectJ, aby wykonać rzeczywiste tkanie kodu bajtowego w Maven (może to nie być minimalny przykład).

<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>

Na koniec dodaj to do swojej klasy config

@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)

Teraz powinieneś być w stanie używać @Transactional na prywatnych metodach.

Jedno zastrzeżenie do tego podejścia: Musisz skonfigurować IDE, aby mieć świadomość AspectJ, w przeciwnym razie, jeśli uruchomisz aplikację za pośrednictwem Eclipse, na przykład może nie działać. Upewnij się, że testujesz na bezpośredniej kompilacji Maven jako sprawdzian zdrowia.

James Watkins
źródło
jeśli metoda proxy to cglib, nie ma potrzeby implementowania interfejsu, którego metoda powinna być publiczna, to może używać @Transactional na metodach prywatnych?
lily
Tak, działa na prywatnych metodach i bez interfejsów! Dopóki AspectJ jest odpowiednio skonfigurowany, zasadniczo gwarantuje dekoratorów metod pracy. A użytkownik536161 wskazał w swojej odpowiedzi, że zadziała to nawet w przypadku samodzielnych wywołań. To naprawdę fajne i trochę przerażające.
James Watkins
12

Jeśli chcesz zawinąć prywatną metodę w transakcji i nie chcesz używać aspektuj, możesz użyć 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(){
        //...
    }

}
loonis
źródło
Dobrze jest pokazać TransactionTemplateużycie, ale proszę wywołać tę drugą metodę ..RequiresTransactionzamiast ..InTransaction. Zawsze wymieniaj rzeczy, które chcesz przeczytać rok później. Chciałbym również zastanowić się, czy naprawdę wymaga drugiej prywatnej metody: albo umieść jego treść bezpośrednio w anonimowej executeimplementacji, albo jeśli stanie się to bałagan, może to wskazywać na podział implementacji na inną usługę, którą możesz następnie opatrzyć adnotacjami @Transactional.
Utknąłem
@ Utknął, druga metoda rzeczywiście nie jest konieczna, ale odpowiada na pierwotne pytanie, jak zastosować transakcję wiosenną na metodzie prywatnej
loonis
tak, już głosowałem za odpowiedzią, ale chciałem podzielić się pewnym kontekstem i przemyśleniami na temat tego, jak ją zastosować, ponieważ myślę, że z punktu widzenia architektury sytuacja ta może wskazywać na wadę projektową.
Utknąłem
5

Spring Docs to wyjaśniają

W trybie proxy (który jest domyślny) przechwytywane są tylko zewnętrzne wywołania metod przychodzące przez proxy. Oznacza to, że samo wywołanie, w efekcie metoda w obiekcie docelowym wywołująca inną metodę obiektu docelowego, nie doprowadzi do faktycznej transakcji w czasie wykonywania, nawet jeśli wywoływana metoda jest oznaczona @Transactional.

Rozważ użycie trybu AspectJ (patrz atrybut trybu w tabeli poniżej), jeśli spodziewasz się, że self-wywołania będą również pakowane w transakcje. W takim przypadku nie będzie proxy; zamiast tego klasa docelowa zostanie utkana (to znaczy, jej kod bajtów zostanie zmodyfikowany) w celu przekształcenia @Transactional w zachowanie w czasie wykonywania dowolnej metody.

Innym sposobem jest użytkownik BeanSelfAware

użytkownik536161
źródło
czy możesz dodać odniesienie BeanSelfAware? Nie wygląda to na klasę
wiosenną
@asgs Załóżmy, że chodzi o samodzielne wstrzyknięcie (zapewnij fasoli instancję samego siebie owiniętą w serwer proxy). Możesz zobaczyć przykłady w stackoverflow.com/q/3423972/355438 .
Lu55
1

W taki sam sposób, jak @loonis sugeruje użycie TransactionTemplate, można użyć tego komponentu pomocniczego (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()
    }
}

Stosowanie:

@Service
class SomeService(private val transactionalUtils: TransactionalUtils) {

    fun foo() {
        transactionalUtils.executeAsTransactional { transactionalFoo() }
    }

    private fun transactionalFoo() {
        println("This method is executed within transaction")
    }
}

Nie wiem, czy TransactionTemplateponownie wykorzystasz istniejącą transakcję, czy nie, ale ten kod zdecydowanie.

Lu55
źródło