Hello

[JPA] Service Layer 에서 DataIntegrityViolationException 처리 본문

java

[JPA] Service Layer 에서 DataIntegrityViolationException 처리

nari0_0 2024. 1. 10. 11:25
728x90

유니크 컬럼에 중복값을 넣으려고 할 때, DataIntegrityViolationException이 발생해 db호출이 일어나는 메소드에서 Exception을 잡아 IllegalArgumentException으로 감싸 넘기려고 했는데 캐치 되지 않고 해당 에러가 리턴되었다.

 

    @Transactional
    public void addMemberPhone(MemberPhoneRequest memberPhone) {
        MemberPhone memberPhone = new MemberPhone();
        ...
        try{
            memberPhoneRepo.save(memberPhone);
        }catch (DataIntegrityViolationException e){
            throw new IllegalArgumentException("이미 존재하는 전화번호 입니다.");
        }
    }

error

Exception이 캐치되지 않는 이유는 JPA의 영속성 컨텍스트와 관련이 있습니다.

  1. @Transactional이 붙은 메소드에서 Repository.save() or saveAll()이 호출될 때 쿼리를 실행하지 않고 쓰기 지연 상태가 되어 1차 캐시에만 쿼리를 보관 하고 있습니다.
  2. 메소드가 정상적으로 종료되었다면 실제 쿼리를 디비에 실행해 DB에 반영하게 됩니다.
  3. try-catch 블럭 내부에서 쿼리가 실행되지 않아 DataIntegrityViolationException이 발생되지 않습니다.
  4. 메소드가 종료된 후에 쿼리가 실행되면서 DataIntegrityViolationException이 발생합니다.

쓰기 지연 (transactional write-behind)

  •  영속성 컨텍스트에 변경이 발생했을 때, 바로 DB로 쿼리를 보내지 않고 쿼리를 1차 캐시에 보관하고 있다가, 영속성 컨텍스트가 flush 하는 시점에 쿼리를 DB로 보냄.

해결방법

  1. saveAndFlush() 메소드 사용
  2. @ExceptionHandler 예외 캐치
    1. Controller 에 작성
    2. 프로젝트 전역 설정 필요한 경우 @ControllerAdvice

첫번째 방법

saveAndFlush() 메소드를 호출해 1차캐시에 쿼리를 보관하지 않고 DB에 바로 실행하도록 해 해당 메소드 내에서 try-catch 처리 가능

 

두,세번째 방법

작성 내용은 동일하되, 에러 캐치 범위에 대한 차이가 있습니다.

- Controller.class에 작성 하는 경우 클래스 내에서 발생한 DataIntegrityViolationException 만 캐치

- 프로젝트 전역에서 DataIntegrityViolationException이 발생한 경우 캐치

    @ExceptionHandler(value = {DataIntegrityViolationException.class})
    public ResponseEntity handleDataIntegrityViolationException(DataIntegrityViolationException e) {
        log.error(e.getMessage(), e);
        return ResponseEntity.badRequest().body(new IllegalArgumentException("중복된 데이터가 존재합니다."));
    }

Service layer 단위 테스트 해당 이슈를 확인할 수 없음,  Controller layer 에서 확인 필요함.

 

+)

@Transactional를 사용하지 않는 경우, 쿼리 실행 메소드마다 flush - commit 동작됨 트랜잭션기능을 사용할 수 없음.

사용하면 안되는 방법임.

 

 

참고 :

https://www.baeldung.com/spring-data-jpa-save-saveandflush

https://www.baeldung.com/spring-dataIntegrityviolationexception

https://colabear754.tistory.com/162

https://stackoverflow.com/questions/41362841/spring-data-cannot-catch-dataintegrityviolationexception-in-service-layer

728x90