복붙노트

[SPRING] JPA 트랜잭션을 커밋 할 수 없음 : rollbackOnly로 표시된 트랜잭션

SPRING

JPA 트랜잭션을 커밋 할 수 없음 : rollbackOnly로 표시된 트랜잭션

Spring과 Hibernate를 사용하고있는 어플리케이션 중 하나에서 트랜잭션을 처리하는 데 문제가 있습니다.

데이터베이스에서 일부 엔티티를로드하고 일부 값을 수정 한 다음 (모든 것이 유효한 경우) 데이터베이스에 이러한 변경 사항을 커밋하는 서비스 클래스가 있습니다. 새 값이 유효하지 않은 경우 (설정 한 후에 만 ​​확인할 수 있음) 변경 사항을 유지하고 싶지 않습니다. 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로 올바르게 설정하고 있지만 롤백이 예외로 인해 충돌하는 이유는 무엇입니까?

해결법

  1. ==============================

    1.내 추측은 ServiceUser.method () 자체가 트랜잭션 적이라는 것이다. 그것은해서는 안됩니다. 이유는 다음과 같습니다.

    내 추측은 ServiceUser.method () 자체가 트랜잭션 적이라는 것이다. 그것은해서는 안됩니다. 이유는 다음과 같습니다.

    ServiceUser.method () 메소드를 호출 할 때 일어나는 일은 다음과 같습니다.

    이제 ServiceUser.method ()가 트랜잭션이 아니라면 다음과 같이됩니다.

  2. ==============================

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

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

    Service1과 Service2라는 두 개의 Spring 서비스가 있다고 가정합니다. 우리 프로그램에서 우리는 Service2.method2 ()를 호출하는 Service1.method1 ()을 호출합니다.

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

    별도로 명시하지 않는 한 SomeException은 선택 취소됩니다 (RuntimeException을 확장).

    시나리오 :

    이 요지에서 테스트 된 모든 시나리오를보십시오.

  3. ==============================

    3.롤백 플래그가 설정되는 원래 예외를 추적하도록 디버거를 설정할 수없는 (또는 원하지 않는) 사용자는 코드 전체에 일련의 디버그 문을 추가하여 줄을 찾을 수 있습니다 롤백 전용 플래그를 트리거하는 코드

    롤백 플래그가 설정되는 원래 예외를 추적하도록 디버거를 설정할 수없는 (또는 원하지 않는) 사용자는 코드 전체에 일련의 디버그 문을 추가하여 줄을 찾을 수 있습니다 롤백 전용 플래그를 트리거하는 코드

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

    코드 전반에 걸쳐이를 추가하면 디버그 문에 번호를 매기고 위의 메소드가 "거짓"에서 "참"으로 돌아가는 것을 확인하여 근본 원인을 좁힐 수있었습니다.

  4. ==============================

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

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

    Transactional.TxType 참조 문서가 여기에 있습니다.

  5. ==============================

    5.하위 객체를 먼저 저장 한 후 최종 저장소 저장 메소드를 호출합니다.

    하위 객체를 먼저 저장 한 후 최종 저장소 저장 메소드를 호출합니다.

    @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";
        }
    
  6. from https://stackoverflow.com/questions/25322658/could-not-commit-jpa-transaction-transaction-marked-as-rollbackonly by cc-by-sa and MIT license