Wiosna - @Transactional - Co dzieje się w tle?


334

Chcę wiedzieć, co się właściwie dzieje, kiedy adnotujesz metodę @Transactional? Oczywiście wiem, że Spring zawinie tę metodę w Transakcji.

Ale mam następujące wątpliwości:

  1. Słyszałem, że Spring tworzy klasę proxy ? Czy ktoś może wyjaśnić to bardziej szczegółowo . Co faktycznie znajduje się w tej klasie proxy? Co stanie się z rzeczywistą klasą? I jak mogę zobaczyć klasę proxy stworzoną przez Springa
  2. Przeczytałem również w dokumentach wiosennych, że:

Uwaga: Ponieważ ten mechanizm jest oparty na serwerach proxy, przechwytywane będą tylko zewnętrzne wywołania metod przychodzące przez serwer 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!

Źródło: http://static.springsource.org/spring/docs/2.0.x/reference/transaction.html

Dlaczego tylko zewnętrzne wywołania metod będą podlegać Transakcji, a nie metody samodzielnego wywoływania?


2
Odpowiednia dyskusja znajduje się tutaj: stackoverflow.com/questions/3120143/…
dma_k

Odpowiedzi:


255

To jest duży temat. Dokument źródłowy z wiosny poświęca mu wiele rozdziałów. Polecam przeczytać te dotyczące programowania i transakcji zorientowanych na aspekty , ponieważ deklaratywne wsparcie transakcji Spring wykorzystuje AOP u podstawy.

Ale na bardzo wysokim poziomie Spring tworzy proxy dla klas, które deklarują @Transactional na samej klasie lub na elementach. Serwer proxy jest w większości niewidoczny w czasie wykonywania. Zapewnia Springowi wstrzykiwanie zachowań przed wywołaniami metod, w ich pobliżu lub w ich pobliżu. Zarządzanie transakcjami jest tylko jednym z przykładów zachowań, które można zaczepić. Kolejne są kontrole bezpieczeństwa. Możesz także podać własne, np. Rejestrowanie. Kiedy więc adnotujesz metodę za pomocą @Transactional , Spring dynamicznie tworzy proxy, które implementuje ten sam interfejs (interfejsy) co klasa, którą adnotujesz. A kiedy klienci wykonują wywołania w twoim obiekcie, połączenia są przechwytywane, a zachowania wprowadzane za pośrednictwem mechanizmu proxy.

Nawiasem mówiąc, transakcje w EJB działają podobnie.

Jak zauważyłeś, mechanizm proxy działa tylko wtedy, gdy przychodzą połączenia z jakiegoś obiektu zewnętrznego. Kiedy wykonujesz wywołanie wewnętrzne w obiekcie, tak naprawdę wykonujesz wywołanie poprzez odwołanie „ to ”, które omija proxy. Istnieją jednak sposoby obejścia tego problemu. Wyjaśniam jedno podejście w tym poście na forum, w którym używam BeanFactoryPostProcessor do wstrzykiwania instancji proxy do klas „samo-referencyjnych” w czasie wykonywania. Zapisuję to odwołanie w zmiennej składowej o nazwie „ ja ”. Następnie, jeśli muszę wykonywać połączenia wewnętrzne, które wymagają zmiany statusu transakcji wątku, kieruję połączenie przez serwer proxy (np. „ Me.someMethod ()".) Wpis na forum wyjaśnia bardziej szczegółowo. Zauważ, że kod BeanFactoryPostProcessor byłby teraz trochę inny, ponieważ został napisany w ramce czasowej Spring 1.x. Mam jednak nadzieję, że daje to pomysł. Mam zaktualizowaną wersję, która Prawdopodobnie mógłbym udostępnić.


4
>> Serwer proxy jest przeważnie niewidoczny w czasie wykonywania Oh! Jestem ciekawy ich zobaczyć :) Odpocznij .. Twoja odpowiedź była bardzo wyczerpująca. To drugi raz, kiedy mi pomagasz. Dzięki za całą pomoc.
peakit

17
Nie ma problemu. Możesz zobaczyć kod proxy, jeśli przejdziesz przez debugger. To chyba najłatwiejszy sposób. Nie ma magii; to tylko zajęcia z pakietów wiosennych.
Rob H

A jeśli metoda, która ma adnotację @Transaction, implementuje interfejs, sprężyna użyje dynamicznego interfejsu API proxy do wprowadzenia transakcji i nie użyje proxy. W każdym razie wolę, aby moje transakcyjne klasy implementowały interfejsy.
Michael Wiles,

1
Znalazłem też schemat „ja” (używając jawnego okablowania, aby zrobić to tak, jak to pasuje do mojego sposobu myślenia), ale myślę, że jeśli robisz to w ten sposób, prawdopodobnie lepiej jest zrefaktoryzować, aby nie musieć. Ale tak, może to czasami być bardzo niezręczne!
Donal Fellows

2
2019: Ponieważ ta odpowiedź się starzeje, odsyłany post na forum nie jest już dostępny, co opisuje przypadek, gdy trzeba wykonać wewnętrzne wywołanie w obiekcie bez pomijania proxy, używającBeanFactoryPostProcessor . Istnieje jednak (moim zdaniem) bardzo podobna metoda opisana w tej odpowiedzi: stackoverflow.com/a/11277899/3667003 ... a także inne rozwiązania w całym wątku.
Z3d4s,

196

Gdy Spring załaduje definicje komponentu bean i zostanie skonfigurowany do wyszukiwania @Transactionaladnotacji, utworzy te obiekty proxy wokół faktycznego komponentu bean . Te obiekty proxy są instancjami klas generowanych automatycznie w czasie wykonywania. Domyślne zachowanie tych obiektów proxy podczas wywoływania metody polega na wywołaniu tej samej metody na fasoli „docelowej” (tj. Fasoli).

Jednak proxy mogą być również dostarczane z przechwytywaczami, a gdy są obecne, przechwyty te zostaną wywołane przez proxy, zanim wywoła metodę fasoli docelowej. W przypadku ziaren docelowych opatrzonych adnotacjami @TransactionalSpring utworzy obiekt TransactionInterceptori przekaże go do wygenerowanego obiektu proxy. Więc kiedy wywołujesz metodę z kodu klienta, wywołujesz metodę na obiekcie proxy, który najpierw wywołuje TransactionInterceptor(która rozpoczyna transakcję), która z kolei wywołuje metodę na fasoli docelowej. Po zakończeniu wywołania TransactionInterceptorzatwierdza / wycofuje transakcję. Jest przezroczysty dla kodu klienta.

Jeśli chodzi o „metodę zewnętrzną”, jeśli twoja fasola wywoła jedną ze swoich własnych metod, nie zrobi tego za pośrednictwem proxy. Pamiętaj, że Spring otacza twoją fasolę proxy, twoja fasola nie ma o niej wiedzy. Tylko połączenia z „zewnątrz” Twojej fasoli przechodzą przez serwer proxy.

To pomaga?


36
> Pamiętaj, że Spring otacza twoją fasolę proxy, twoja fasola nie ma o tym pojęcia. To wszystko powiedziało. Co za świetna odpowiedź. Dzięki za pomoc.
peakit

Świetne wyjaśnienie dla proxy i przechwytujących. Teraz rozumiem, że wiosną zaimplementuj obiekt proxy, aby przechwytywać połączenia z komponentem bean. Dziękuję Ci!
dharag

Myślę, że próbujesz opisać to zdjęcie dokumentacji wiosennej, a zobaczenie tego zdjęcia bardzo mi pomaga: docs.spring.io/spring/docs/4.2.x/spring-framework-reference/…
WesternGun

44

Jako osoba wizualna lubię ważyć schemat sekwencji wzoru proxy. Jeśli nie wiesz, jak czytać strzały, czytam pierwszą w ten sposób: Clientwykonuje Proxy.method().

  1. Klient wywołuje metodę na celu z jego perspektywy i jest cicho przechwytywany przez serwer proxy
  2. Jeśli zdefiniowano aspekt przed, serwer proxy go wykona
  3. Następnie wykonywana jest faktyczna metoda (cel)
  4. Powracanie i wyrzucanie to opcjonalne aspekty, które są wykonywane po zwróceniu metody i / lub jeśli metoda zgłosi wyjątek
  5. Następnie proxy wykonuje aspekt po (jeśli zdefiniowano)
  6. Wreszcie serwer proxy wraca do klienta wywołującego

Diagram sekwencji wzorów proxy (Mogłem opublikować zdjęcie pod warunkiem, że wspomniałem o jego pochodzeniu. Autor: Noel Vaes, strona internetowa: www.noelvaes.eu)


27

Najprostsza odpowiedź to:

Niezależnie od metody zadeklarujesz @Transactionalgranicę rozpoczęcia i zakończenia transakcji po jej zakończeniu.

Jeśli używasz wywołania JPA, wszystkie zatwierdzenia są w tej granicy transakcji .

Powiedzmy, że oszczędzasz byt1, byt2 i byt3. Teraz podczas zapisywania entity3 wyjątek występuje , a następnie jako enitiy1 i entity2 jest w tej samej transakcji tak entity1 i entity2 będzie wycofywania z entity3.

Transakcja :

  1. entity1.save
  2. podmiot2.save
  3. entity3.save

Jakikolwiek wyjątek spowoduje wycofanie wszystkich transakcji JPA z DB. Wewnętrznie transakcje JPA są używane przez Spring.


2
„Wyjątek A̶n̶y̶ spowoduje wycofanie wszystkich transakcji JPA z DB.” Uwaga Tylko RuntimeException powoduje wycofanie. Sprawdzone wyjątki nie spowodują wycofania.
Arjun

2

Może być późno, ale natknąłem się na coś, co wyjaśnia twoje obawy związane z proxy (ładnie przechwycone będą tylko wywołania metod „zewnętrznych” przychodzące przez proxy).

Na przykład masz klasę, która wygląda tak

@Component("mySubordinate")
public class CoreBusinessSubordinate {

    public void doSomethingBig() {
        System.out.println("I did something small");
    }

    public void doSomethingSmall(int x){
        System.out.println("I also do something small but with an int");    
  }
}

i masz aspekt, który wygląda następująco:

@Component
@Aspect
public class CrossCuttingConcern {

    @Before("execution(* com.intertech.CoreBusinessSubordinate.*(..))")
    public void doCrossCutStuff(){
        System.out.println("Doing the cross cutting concern now");
    }
}

Kiedy wykonasz to w ten sposób:

 @Service
public class CoreBusinessKickOff {

    @Autowired
    CoreBusinessSubordinate subordinate;

    // getter/setters

    public void kickOff() {
       System.out.println("I do something big");
       subordinate.doSomethingBig();
       subordinate.doSomethingSmall(4);
   }

}

Wyniki wywołania kickOff powyżej podanego powyżej kodu.

I do something big
Doing the cross cutting concern now
I did something small
Doing the cross cutting concern now
I also do something small but with an int

ale kiedy zmienisz kod na

@Component("mySubordinate")
public class CoreBusinessSubordinate {

    public void doSomethingBig() {
        System.out.println("I did something small");
        doSomethingSmall(4);
    }

    public void doSomethingSmall(int x){
       System.out.println("I also do something small but with an int");    
   }
}


public void kickOff() {
  System.out.println("I do something big");
   subordinate.doSomethingBig();
   //subordinate.doSomethingSmall(4);
}

Widzisz, metoda wewnętrznie wywołuje inną metodę, więc nie zostanie przechwycona, a wynik będzie wyglądał następująco:

I do something big
Doing the cross cutting concern now
I did something small
I also do something small but with an int

Możesz to ominąć, robiąc to

public void doSomethingBig() {
    System.out.println("I did something small");
    //doSomethingSmall(4);
    ((CoreBusinessSubordinate) AopContext.currentProxy()).doSomethingSmall(4);
}

Fragmenty kodu pobrane z: https://www.intertech.com/Blog/secrets-of-the-spring-aop-proxy/


0

Wszystkie istniejące odpowiedzi są poprawne, ale nie mogę podać tylko tego złożonego tematu.

W celu uzyskania kompleksowego, praktycznego wyjaśnienia możesz zajrzeć do przewodnika Spring @Transactional Dogłębny , który stara się objąć zarządzanie transakcjami w ~ 4000 prostych słowach, z dużą ilością przykładów kodu.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.