2012-11-20 10 views
9

Mam następujący kod:Grails UnexpectedRollbackException wystąpiły: Nie wiem, dlaczego

class ServiceA { 

    def save(Object object) { 
     if (somethingBadComesBack) { 
     throw new CustomRuntimeException(data) 
     } 
    } 
} 

class ServiceB { 

    def serviceA 

    def save(Object object) { 
     try { 
     serviceA.save(object) 
     // do more stuff if good to go 
     } catch(CustomRuntimeException e) { 
     // populate some objects with errors based on exception 
     } 
    } 
} 

class ServiceC { 

    def serviceB 

    def process(Object object) { 
     serviceB.save(object) 
     if (object.hasErrors() { 
      // do some stuff 
     }else{ 
     // do some stuff 
     } 

     def info = someMethod(object) 
     return info 
    } 
} 

class SomeController { 

    def serviceC 

    def process() { 

    def object = ..... 
    serviceC.save(object) // UnexpectedRollbackException is thrown here 

    } 
} 

Kiedy ServiceA.save() nazywa i wystąpi wyjątek, ServiceC.save() się rzuca UnexpectedRollbackException gdy próbuje wrócić.

Zrobiłem następujące:

try { 
    serviceC.process(object) 
}catch(UnexpectedRollbackException e) { 
    println e.getMostSpecificCause() 
} 

i otrzymuję:

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only 

nie jestem pewien, gdzie zacząć szukać, jak to naprawić.

Odpowiedz

11

Używasz wyjątku środowiska wykonawczego do wycofania transakcji, ale to oszustwo - wykorzystuje efekt uboczny. Wyjątki w czasie wykonywania wycofują transakcje automatycznie, ponieważ nie ma potrzeby ich przechwytywania, więc zakłada się, że jeśli zostanie rzucony, nie był oczekiwany, a domyślnym zachowaniem jest wycofywanie transakcji. Można skonfigurować metody, aby nie wycofywały się z określonych przewidywanych wyjątków czasu wykonywania, ale jest to dość rzadkie. Sprawdzane wyjątki nie powodują wycofywania wyjątków, ponieważ w Javie muszą być przechwycone lub zadeklarowane w throws, więc musisz je jawnie rzucić lub schylić; tak czy inaczej miałeś okazję spróbować ponownie.

Poprawny sposób celowo cofnąć transakcji jest wywołanie setRollbackOnly() o aktualnym TransactionStatus ale nie jest dostępny bezpośrednio w metodzie usługi (to jest w withTransaction bloku, ponieważ jest to argument do zamknięcia). Ale łatwo jest: importować org.springframework.transaction.interceptor.TransactionAspectSupport i dzwonić pod numer TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(). Będzie to wymagało zmiany kodu, ponieważ nie będzie wyjątku do przechwytywania, więc musisz sprawdzić, czy został on wycofany z TransactionAspectSupport.currentTransactionStatus().isRollbackOnly().

Nie jestem pewien, czy jest to problem Grails lub standardowe zachowanie, ale gdy debugowałem to, były 3 odwołania do commitowania z 3 różnymi instancjami TransactionStatus. Tylko pierwsza miała ustawioną flagę wycofywania, ale druga była świadoma pierwszej i była w porządku. Trzecia była uważana za nową transakcję i była tą, która wywołała ten sam wyjątek, który widziałeś. Aby obejść to, dodałem to do 2. i 3. metody obsługi:

def status = TransactionAspectSupport.currentTransactionStatus() 
if (!status.isRollbackOnly()) status.setRollbackOnly() 

, aby łańcuch flagi wycofywania. To zadziałało i nie dostałem UnexpectedRollbackException.

Może być łatwiej połączyć to ze sprawdzonym wyjątkiem. Nadal jest to kosztowne, ponieważ niepotrzebnie zapełnia on stos, ale jeśli zadzwonisz pod numer setRollbackOnly() i wyrzucisz zaznaczony wyjątek, będziesz mógł użyć tego samego ogólnego przepływu pracy, jaki masz teraz.

+0

Dzięki Burt. Będziemy bawić się z tym podejściem i zobaczyć, co mogę dostać. Odpowiem z powrotem ... – Gregg

+2

Grails 2.3.7 będzie zawierał domyślną funkcję obsługi tej sytuacji: http://jira.grails.org/browse/GRAILS-11145 –

+0

@FlareCoder - Dziękujemy za udostępnienie tego! Właśnie zaktualizowałem grę z wersji 2.3.6 do 2.3.7 i problem został rozwiązany. – arcseldon

0

Wygląda na to, że Cię gryzie default transactionality of services, a niezaznaczony wyjątek wyrzucony w Usłudze A przerywa transakcję tylko po jej złapaniu.

Powyższe dokumenty mówią, że poziom propagacji txn to PROPAGATION_REQUIRED, co powinno oznaczać, że ta sama transakcja jest udostępniana z usługi C na poziomie A, jeśli moja pamięć mnie obsługuje. Czy można użyć metody Service A w celu sprawdzenia wyjątku, zamiast wyjątku RuntimeException, aby uniknąć automatycznego wycofywania z tego ostatniego? Lub wyłączyć transakcje w swoich usługach, jeśli jest to opcja dla Ciebie?

Powiązane problemy