programing

JPA 트랜잭션을 커밋할 수 없습니다.트랜잭션이 rollbackOnly로 표시됨

mailnote 2023. 10. 18. 22:59
반응형

JPA 트랜잭션을 커밋할 수 없습니다.트랜잭션이 rollbackOnly로 표시됨

작업 중인 애플리케이션 중 하나에서 스프링과 하이버네이트를 사용하고 있는데 트랜잭션 처리에 문제가 있습니다.

데이터베이스에서 일부 엔티티를 로드하고 일부 값을 수정한 다음 (모든 것이 유효한 경우) 이러한 변경 사항을 데이터베이스에 커밋하는 서비스 클래스가 있습니다.새 값이 잘못된 경우(설정한 후에만 확인 가능) 변경 사항을 지속하고 싶지 않습니다.Spring/Hibernate에서 변경 내용을 저장하지 못하도록 메소드에 예외를 적용합니다.그러나 이 경우 다음 오류가 발생합니다.

Could not commit JPA transaction: Transaction marked as rollbackOnly

그리고 이것이 서비스입니다.

@Service
class MyService {

  @Transactional(rollbackFor = MyCustomException.class)
  public void doSth() throws MyCustomException {
    //load entities from database
    //modify some of their values
    //check if they are valid
    if(invalid) { //if they arent valid, throw an exception
      throw new MyCustomException();
    }

  }
}

이렇게 불러봅니다.

class ServiceUser {
  @Autowired
  private MyService myService;

  public void method() {
    try {
      myService.doSth();
    } catch (MyCustomException e) {
      // ...
    }        
  }
}

내가 예상했던 일:데이터베이스에 변경사항이 없으며 사용자에게 표시되는 예외도 없습니다.

발생 상황:데이터베이스에 대한 변경 사항은 없지만 앱이 다음과 함께 충돌합니다.

org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction;
nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly

트랜잭션을 rollbackOnly(롤백 전용)로 올바르게 설정하고 있는데 롤백이 예외로 충돌하는 이유는 무엇입니까?

내 추측으로는ServiceUser.method()그 자체가 거래적인 것입니다.그러면 안 됩니다.이유는 이렇습니다.

전화가 걸려오면 다음과 같이 됩니다.ServiceUser.method()방법:

  1. 트랜잭션 인터셉터가 메서드 호출을 가로채고 트랜잭션을 시작합니다. 트랜잭션이 이미 활성화되어 있지 않기 때문입니다.
  2. 그 방법은 라고 불립니다.
  3. 메서드는 MyService.doSth()를 호출합니다.
  4. 트랜잭션 인터셉터는 메소드 호출을 가로채고 트랜잭션이 이미 활성화되어 있음을 확인하고 아무것도 하지 않습니다.
  5. doSth()가 실행되고 예외가 발생합니다.
  6. 트랜잭션 가로채기가 예외를 가로채서 트랜잭션을 rollbackOnly로 표시하고 예외를 전파합니다.
  7. ServiceUser.method()가 예외를 잡고 반환합니다.
  8. 트랜잭션 가로채기는 트랜잭션을 시작했기 때문에 커밋을 시도합니다.그러나 Hibernate는 트랜잭션이 rollbackOnly로 표시되므로 Hibernate는 예외를 발생시킵니다.트랜잭션 인터셉터는 최대 절전 모드 예외를 래핑하는 예외를 던져 호출자에게 신호를 보냅니다.

만약에ServiceUser.method()거래가 아닙니다. 다음과 같은 일이 발생합니다.

  1. 그 방법은 라고 불립니다.
  2. 메서드는 MyService.doSth()를 호출합니다.
  3. 트랜잭션 인터셉터는 메서드 호출을 가로채고 이미 활성화된 트랜잭션이 없음을 확인하고 트랜잭션을 시작합니다.
  4. doSth()가 실행되고 예외가 발생합니다.
  5. 트랜잭션 인터셉터가 예외를 가로채는 것입니다.거래를 시작했기 때문에, 그리고 예외가 던져졌기 때문에, 그것은 거래를 롤백하고, 예외를 전파합니다.
  6. ServiceUser.method()가 예외를 잡고 반환합니다.

JPA 트랜잭션을 커밋할 수 없습니다.트랜잭션이 rollbackOnly로 표시됨

으로 표시된 중첩 메서드/서비스를 호출할 때 이 예외가 발생합니다. JBNizet에서 메커니즘에 대해 자세히 설명했습니다.저는 그것이 발생했을 때의 시나리오그것을 피할있는 몇 가지 방법을 추가하고 싶습니다.

두 가지 Spring 서비스가 있다고 가정해 보겠습니다.Service1그리고.Service2. 우리가 부르는 프로그램에서.Service1.method1()차례로 전화를 걸어오는Service2.method2():

class Service1 {
    @Transactional
    public void method1() {
        try {
            ...
            service2.method2();
            ...
        } catch (Exception e) {
            ...
        }
    }
}

class Service2 {
    @Transactional
    public void method2() {
        ...
        throw new SomeException();
        ...
    }
}

SomeException선택 취소됨(Runtime 확장)예외) 특별한 언급이 없는 한.

시나리오:

  1. 예외에서 제외되어 롤백으로 표시된 트랜잭션method2 JB Nizet에서 설명한 저희의 디폴트 케이스입니다.

  2. 주석 달기method2~하듯이@Transactional(readOnly = true)롤백을 위해 트랜잭션을 표시합니다(종료 시 던지는 exception).method1).

  3. 둘 다 주석 달기method1그리고.method2~하듯이@Transactional(readOnly = true)롤백을 위해 트랜잭션을 표시합니다(종료 시 던지는 exception).method1).

  4. 주석 달기method2와 함께@Transactional(noRollbackFor = SomeException)롤백을 위해 트랜잭션을 표시할 수 없음(에서 나갈 때 예외가 발생하지 않음)method1).

  5. 가정하다method2에 속해있습니다.Service1하는 중입니다.method1스프링의 대리인을 거치지 않습니다. 즉, 스프링은 이를 알지 못합니다.SomeException에서 쫓겨난method2. 이 경우 트랜잭션은 롤백으로 표시되지 않습니다.

  6. 가정하다method2에 주석을 달지 않았습니다.@Transactional하는 중입니다.method1스프링의 대리인을 거치기는 하지만 스프링은 예외를 인정하지 않습니다.이 경우 트랜잭션은 롤백으로 표시되지 않습니다.

  7. 주석 달기method2와 함께@Transactional(propagation = Propagation.REQUIRES_NEW)만든다method2거래를 새로 시작합니다.해당 두 번째 트랜잭션은 종료 시 롤백으로 표시됩니다.method2그러나 이 경우 원래 트랜잭션은 영향을 받지 않습니다(에서 나갈 때 예외가 발생하지 않음).method1).

  8. 에 같은 .SomeException선택됨(Runtime을 확장하지 않음)예외), 스프링은 기본적으로 확인된 예외를 가로챌 때 롤백을 위한 트랜잭션을 표시하지 않습니다(에서 나갈 때 예외가 표시되지 않음).method1).

이 요지에서 테스트한 모든 시나리오를 참조하십시오.

롤백 플래그가 설정되도록 하는 원래 예외를 추적하기 위해 디버거를 설정할 수 없거나 원하지 않는 사람은 코드 전체에 디버그 문을 여러 개 추가하여 롤백 전용 플래그를 트리거하는 코드 라인을 찾을 수 있습니다.

logger.debug("Is rollbackOnly: " + TransactionAspectSupport.currentTransactionStatus().isRollbackOnly());

코드 전체에 이것을 추가하면 디버그 문에 번호를 부여하고 위 방법이 "거짓"을 "참"으로 반환하는 것에서 어디로 가는지 확인함으로써 근본 원인을 좁힐 수 있었습니다.

@Yaroslav Stavnichiy가 설명한 것처럼, 만약 어떤 서비스가 거래 스프링으로 표시되어 있다면 거래 자체를 처리하려고 합니다.예외가 발생하면 롤백 작업이 수행됩니다.시나리오에서 ServiceUser.method()가 트랜잭션 작업을 수행하지 않는 경우 @Transactional을 사용할 수 있습니다.TxType 주석입니다.'NEVER' 옵션은 트랜잭션 컨텍스트 외부에서 해당 메서드를 관리하는 데 사용됩니다.

거래.TxType 참조 문서가 있습니다.

하위 개체를 먼저 저장한 다음 최종 리포지토리 저장 방법을 호출합니다.

@PostMapping("/save")
    public String save(@ModelAttribute("shortcode") @Valid Shortcode shortcode, BindingResult result) {
        Shortcode existingShortcode = shortcodeService.findByShortcode(shortcode.getShortcode());
        if (existingShortcode != null) {
            result.rejectValue(shortcode.getShortcode(), "This shortode is already created.");
        }
        if (result.hasErrors()) {
            return "redirect:/shortcode/create";
        }
        **shortcode.setUser(userService.findByUsername(shortcode.getUser().getUsername()));**
        shortcodeService.save(shortcode);
        return "redirect:/shortcode/create?success";
    }

저장을 사용하여 null 값이 아닌 null 필드를 null 값으로 업데이트하려고 할 때 제약 조건 위반으로 인해 발생했습니다.

언급URL : https://stackoverflow.com/questions/25322658/could-not-commit-jpa-transaction-transaction-marked-as-rollbackonly

반응형