toy/AgileHub

무결성 제약조건이 위배되는 경우에 대한 예외처리는 어디서 할까

EVO. 2024. 3. 27. 16:32

문제 상황

같은 키를 가진 프로젝트를 생성하면 UNIQUE 제약조건에 의해 예외(DataIntegrityViolationException)가 발생한다.


4가지 방법

1. 서비스계층에서 flush + try catch

엔티티를 저장하고 unique key에 의해 중복이 감지되면, try catch를 통해 예외 변화를 하면 된다.

flush를 해야하는 이유는 @Transactional 이 붙은 메서드에서 사용되는 CrudRepository의 save()와 saveAll()은 메서드를 호출할 때 바로 쿼리를 실행하지 않고 1차캐시에 우선적으로 저장했다가 메서드가 정상적으로 종료되면 커밋이 되어 실제 DB에 반영된다. try-catch 블럭 내부에서는 쿼리가 실행되지 않으니 잡아낼 DataIntegrityViolationException도 발생하지 않았다가, 메소드가 종료된 후에 쿼리가 실행되면서 DataIntegrityViolationException이 발생한다.

그래서 saveAndFlush 를 사용하여 flush하여 예외가 try-catch에 잡히도록 한다.

 

2. @RestControllerAdvice에서 전역적인 예외처리

DataIntegrityViolationException 을 잡아서 한 번에 IllegalArgumentException으로 변환

다만 이 엔티티가 중복인지 메세지에 추가로 담기에는 한계 , 다만 약속한 HTTP 응답 status 혹은 약속된 포맷으로 중복임을 유추가능

3. 중복이 발생한 경우 로직을 넣고 싶을 때

MySQL을 사용하고 있다면 유저 락을 이용할 수 있습니다! 유저 락을 활용하면, 서버 여러 대가 동작하고 있더라도 특정 Service 로직이 한 곳에서만 호출되고 있음을 보장할 수 있습니다. 이를 응용하면, 책을 저장하기 전, 동일한 이름으로 책이 저장되어 있는지 확인하는 코드를 100% 활용할 수 있게 되죠. 물론, 중복이 발생했는데 추가적으로 로직을 넣는 경우는 거의 드뭅니다!  라고 한다. https://www.inflearn.com/questions/856796

 

4. 로직으로 해결하기

해당 답변 참조: https://www.inflearn.com/questions/37376

 

DataIntegrityViolationException 은 크게 보면 데이터 엑세스 계층의 구체적인 예외이다.

데이터베이스에서 일어나는 예외는 일어날 확률이 적어야 하고 만약 일어나면 해당 예외를 잡아서 복구하는 것보다는 그냥 일어나도록 냅두는 것이 옳은 방향.

반면에 애플리케이션 로직에서 해당 무결성 제약조건이 자주 호출되어야 한다면, 먼저 체크 로직을 태운다.

 

결론

해당 서비스는 프로젝트를 생성할 때 같은 키가 있으면 무결성 제약조건이 일어난다. 이렇게 키가 중복되는 일은 자주 일어나는 자연스러운 현상이다. 따라서 앞에 체크 로직을 통해 있는 지 판단하고 200이든 400이든 자세한 메세지를 클라이언트에 전달하는게 더 좋은 방향이라고 본다.