2012-11-20 5 views
9

나는 다음과 같은 코드가 있습니다 ServiceA.save()가 호출 예외가 발생 될 때 반환 할 때Grails의 UnexpectedRollbackException가 발생 모르겠 음 왜

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 

    } 
} 

ServiceC.save()UnexpectedRollbackException을 던지고있다.

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

나는 점점 오전 :

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

나는이 문제를 해결하는 방법을 찾고 어디서부터 시작 모르겠어요

나는 다음과 같은했다.

답변

11

트랜잭션을 롤백하기 위해 런타임 예외를 사용하고 있지만 부작용이 있습니다. 부작용을 이용하고 있습니다. 런타임 예외는 트랜잭션을 포착 할 필요가 없으므로 자동으로 트랜잭션을 롤백합니다. 따라서 예외가 발생할 경우 예상하지 않았으며 기본 동작은 롤백하는 것으로 가정합니다. 특정 예상 런타임 예외에 대해 롤백하지 않도록 메소드를 구성 할 수는 있지만 이는 드문 경우입니다. 확인 된 예외는 자바에서 throws에 잡히거나 선언되어야하기 때문에 예외를 롤백하지 않으므로 명시 적으로 던져 넣거나 버리면됩니다. 어느쪽으로 든 다시 시도 할 기회가있었습니다.

의도적으로 트랜잭션을 롤백하는 올바른 방법은 현재 TransactionStatussetRollbackOnly()를 호출하는 것입니다하지만이 서비스 방법 (가 폐쇄에 대한 인수 이후 그것은 withTransaction 블록에있다)에서 직접 액세스 할 수 없습니다. 하지만 도착 방법은 간단합니다. org.springframework.transaction.interceptor.TransactionAspectSupport을 가져오고 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()으로 전화하십시오. catch 할 예외가 없으므로 코드를 다시 작성해야하므로 TransactionAspectSupport.currentTransactionStatus().isRollbackOnly()으로 롤백했는지 확인해야합니다.

Grails 문제인지 표준 동작인지 확실하지 않지만 디버깅 중에는 3 가지 다른 TransactionStatus 인스턴스가있는 3 개의 커밋 호출이있었습니다. 첫 번째 만 롤백 플래그를 설정했지만 두 번째는 첫 번째를 인식하고 확인되었습니다. 세 번째 트랜잭션은 새 트랜잭션으로 간주되어보고 있던 예외와 동일한 예외를 트리거 한 트랜잭션입니다. 그래서 2 층과 3 서비스 방법이 추가이 문제를 해결하려면 : 체인

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

롤백 플래그. 그게 효과가 있었고 나는 UnexpectedRollbackException을 얻지 못했습니다.

확인 된 예외와 결합하는 것이 더 쉽습니다. 불필요하게 stacktrace를 채울 것이기 때문에 여전히 지나치게 비싸지 만 setRollbackOnly()을 호출하고 확인 된 예외를 throw하면 현재와 동일한 일반 워크 플로우를 사용할 수 있습니다.

+0

감사합니다. Burt. 이 접근법을 가지고 놀고 내가 무엇을 얻을 수 있는지 봅니다. 다시보고 할게 ... – Gregg

+2

Grails 2.3.7에는 기본적으로이 상황을 처리하는 기능이 포함되어 있습니다 : http://jira.grails.org/browse/GRAILS-11145 –

+0

@FlareCoder - 공유해 주셔서 감사합니다 !! 방금 grails 2.3.6에서 2.3.7로 업그레이드했고 문제가 해결되었습니다. – arcseldon

0

default transactionality of services이 당신을 물어 뜯고있는 것처럼 보이며, 서비스 A에 던져진 검사되지 않은 예외는 일단 잡히더라도 트랜잭션을 롤백 만합니다.

위의 문서는 txn 전파 수준이 PROPAGATION_REQUIRED이라고 말합니다. 이는 내 메모리가 나를 서비스하는 경우 서비스 C에서 서비스 A까지 동일한 트랜잭션이 공유되어야 함을 의미합니다. Service A의 save 메소드가 RuntimeException 대신 체크 된 예외를 던져서 자동 롤백을 피할 수 있습니까? 또는 귀하의 서비스에 대한 거래를 비활성화 할 수 있습니까?

관련 문제