2016-04-06 12 views
8

Moja aplikacja ładuje listę elementów, które powinny zostać przetworzone. Dzieje się to w klasie, która wykorzystuje harmonogramCzy powinienem przekazać zarządzany podmiot do metody wymagającej nowej transakcji?

@Component 
class TaskScheduler { 

    @Autowired 
    private TaskRepository taskRepository; 

    @Autowired 
    private HandlingService handlingService; 

    @Scheduled(fixedRate = 15000) 
    @Transactional 
    public void triggerTransactionStatusChangeHandling() { 
     taskRepository.findByStatus(Status.OPEN).stream() 
           .forEach(handlingService::handle); 
    } 
} 

W moich HandlingService procesów każde zadanie w issolation użyciu REQUIRES_NEW na poziomie propagacji.

@Component 
class HandlingService { 

    @Transactional(propagation = Propagation.REQUIRES_NEW) 
    public void handle(Task task) { 
     try { 
      processTask(task); // here the actual processing would take place 
      task.setStatus(Status.PROCCESED); 
     } catch (RuntimeException e) { 
      task.setStatus(Status.ERROR); 
     } 
    } 
} 

Kod działa tylko dlatego zacząłem transakcji nadrzędnej na TaskScheduler klasie. Po usunięciu adnotacji @Transactional obiekty nie są już zarządzane, a aktualizacja encji zadania nie jest propagowana do bazy danych. Nie wydaje mi się naturalne, aby zaplanowana metoda była transakcyjna.

Z tego co widzę, mam dwie opcje:

1. Kod Przechowywać jak to jest dzisiaj.

  • Może `s tylko ja i to jest prawidłowa aproach.
  • Ten wariant ma najmniejszą liczbę odwiedzin w bazie danych.

2. Wyjąć @Transactional adnotacji z terminarza, przechodzą identyfikator zadania i przeładować podmiot zadania w HandlingService.

@Component 
class HandlingService { 

    @Autowired 
    private TaskRepository taskRepository; 

    @Transactional(propagation = Propagation.REQUIRES_NEW) 
    public void handle(Long taskId) { 
     Task task = taskRepository.findOne(taskId); 
     try { 
      processTask(task); // here the actual processing would take place 
      task.setStatus(Status.PROCCESED); 
     } catch (RuntimeException e) { 
      task.setStatus(Status.ERROR); 
     } 
    } 
} 
  • ma więcej wyjazdów do bazy danych (jeden dodatkowy zapytań/element)
  • mogą być wykonywane przy użyciu @Async

Czy możesz zaoferować swoją opinię na temat, który jest poprawny sposób radzenia sobie z tego rodzaju problemami, być może z inną metodą, o której nie wiedziałem?

Odpowiedz

9

Jeśli Twoim zamiarem jest przetwarzanie każdego zadania w oddzielnej transakcji, to pierwsze podejście faktycznie nie działa, ponieważ wszystko jest zatwierdzone na końcu transakcji programu planującego.

Powodem tego jest fakt, że w transakcjach zagnieżdżonych instancje są zasadniczo odłączonymi jednostkami (Session s uruchomione w zagnieżdżonych transakcjach nie są znane o tych wystąpieniach). Pod koniec transakcji programu planującego Hibernacja wykonuje sprawdzanie zainfekowanych instancji i synchronizuje zmiany z bazą danych.

Takie podejście jest również bardzo ryzykowne, ponieważ mogą wystąpić problemy, jeśli spróbujesz uzyskać dostęp do niezainicjowanego proxy w instancji Task w zagnieżdżonej transakcji. Mogą wystąpić problemy, jeśli zmienisz wykres obiektów Task w zagnieżdżonej transakcji, dodając do niej inną instancję jednostki załadowaną w zagnieżdżonej transakcji (ponieważ ta instancja zostanie odłączona, gdy formant wróci do transakcji programu planującego).

Z drugiej strony, twoje drugie podejście jest poprawne i proste i pomaga uniknąć wszystkich powyższych pułapek. Tylko, chciałbym odczytać identyfikatory i zatwierdzić transakcję (nie ma potrzeby wstrzymywania jej w trakcie przetwarzania zadań).Najprostszym sposobem na to jest usunięcie adnotacji Transactional z harmonogramu i uczynienie repozytorium metodą transakcyjną (jeśli nie jest już transakcyjna).

Jeśli (i tylko jeśli) wydajność drugiego podejścia jest problemem, jak już wspomniano, możliwe jest przetwarzanie asynchroniczne lub nawet równoległe przetwarzanie w pewnym stopniu. Możesz również rzucić okiem na extended sessions (rozmowy), może uda ci się znaleźć odpowiednie dla twojego przypadku użycia.

+0

Czy w tym przykładzie pamięć podręczna sesji zagnieżdżonej transakcji zostanie zsynchronizowana z sesją zewnętrznej transakcji? Na przykład, jeśli jednostka "Zadanie" zostanie zmieniona w zagnieżdżonej transakcji, czy ta zmiana będzie obowiązywać również w sesji transakcji? – froi

+0

Po udzieleniu odpowiedzi na moje pytanie, gdy sesja transakcji zewnętrznej zostanie przepłukana, czy oznacza to, że zmiany w Zadań będą liczone jako "nieaktualne" zmiany? – froi

1

Zakładając, że processTask(task); to metoda w klasie HandlingService (taki sam jak handle(task) metody), następnie usunięcie @Transactional w HandlingService nie będzie działać z powodu naturalnego zachowania dynamicznego proxy sprężyny.

Cytując spring.io forum:

Kiedy Wiosna ładuje TestService go owinąć go z prokurentem. Jeśli wywołasz metodę TestService poza usługą TestService, proxy zostanie wywołane, a transakcja będzie zarządzana poprawnie. Jednak jeśli wywołasz metodę transakcyjną w metodzie w tym samym obiekcie, nie wywołasz proxy, ale bezpośrednio cel proxy, a nie wykonasz kodu opakowanego wokół usługi, aby zarządzać transakcją.

This is one of SO thread na ten temat, a Oto kilka artykułów na ten temat:

  1. http://tutorials.jenkov.com/java-reflection/dynamic-proxies.html
  2. http://tutorials.jenkov.com/java-persistence/advanced-connection-and-transaction-demarcation-and-propagation.html
  3. http://blog.jhades.org/how-does-spring-transactional-really-work/

Jeśli naprawdę nie lubię dodawanie adnotacji @Transaction w twoim @Scheduled met hod, można uzyskać transakcję z EntityManager i zarządzać transakcję programowo, na przykład:

UserTransaction tx = entityManager.getTransaction(); 
try { 
    processTask(task); 
    task.setStatus(Status.PROCCESED); 
    tx.commit(); 
} catch (Exception e) { 
    tx.rollback(); 
} 

Ale wątpię, że będzie wziąć tę drogę (dobrze, nie będę). W końcu

Czy możesz zaoferować swoją opinię na temat, który jest poprawny sposób rozwiązania tego rodzaju problemów

nie ma poprawny sposób w tym przypadku. Moja osobista opinia jest taka, że ​​adnotacja (na przykład @Transactional) jest tylko znacznikiem i potrzebujesz procesora adnotacji (w tym przypadku sprężyny), aby zadziałał @Transactional. Adnotacja nie będzie miała żadnego wpływu bez procesora.

I Will więcej martwić, na przykład, dlaczego mam processTask(task) i task.setStatus(Status.PROCESSED); mieszkasz poza processTask(task) jeśli wygląda jak robi to samo, itp

HTH.

+0

Nawet jeśli metoda 'processTask' jest prywatną metodą w ramach' HandlingService', wszystkie aktualizacje powinny być propagowane do bazy danych, ponieważ nowa transakcja została otwarta po wywołaniu metody 'handle' (choć jest to proxy Spring). W drugim wariancie zaproponowałem usunięcie '@ Transctional' z harmonogramu i przekazanie id's do usługi obsługi (która otworzy transakcje dla każdego przetwarzanego zadania) – mvlupan

2

Aktualny kod przetwarza zadanie w zagnieżdżonej transakcji, ale aktualizuje status zadania w transakcji zewnętrznej (ponieważ obiekt zadania jest zarządzany przez transakcję zewnętrzną). Ponieważ są to różne transakcje, możliwe jest, że jeden się powiedzie, a drugi nie, pozostawiając bazę danych w niespójnym stanie.W szczególności za pomocą tego kodu ukończone zadania pozostają w statusie otwartym, jeśli przetwarzanie innego zadania spowoduje zgłoszenie wyjątku lub serwer zostanie zrestartowany, zanim wszystkie zadania zostaną przetworzone.

Jak pokazuje Twój przykład, przekazywanie zarządzanych podmiotów do innej transakcji powoduje, że nie jest jednoznaczne, która transakcja powinna aktualizować te podmioty, i dlatego najlepiej tego unikać. Zamiast tego należy przekazywać identyfikatory (lub odłączone jednostki) i unikać niepotrzebnego zagnieżdżania transakcji.

Powiązane problemy