Spring Framework 5.2.8 이하 버전에서 DB Duplicate Entry에 대해 DuplicateKeyException, DataIntegrityViolationException가 혼용되어 발생하는 문제

김동균·2023년 9월 16일

어느 날 슬랙에 오류 응답에 대한 문의가 들어왔다.
우리 팀이 개발하는 서버에서는 비즈니스 로직에서 발생하는 예외는 모두 Custom Exception으로 떨궈주고 있는데, 처음 보는 유형의 에러 응답이 떨어지고 있다는 것이었다.

로그를 확인해보니, Duplicate Entry 발생으로 인한 에러였고, 원래대로라면 상황에 맞는 Custom Exception이 발생했어야 하는 상황이었다.
원래대로라면 Insert 과정에서 Duplicate Entry가 발생하는 경우 DuplicateKeyException를 Catch하여 Custom Exception을 던지는데, DuplicateKeyException가 아닌 DataIntegrityViolationException 이 발생하여 예외를 제대로 던지지 못한 것이었다.
DataIntegrityViolationException

Spring은 데이터베이스 벤더별로 상이한 에러 코드에 대해 Spring에서 추상화한 DataAccessException 의 서브 클래스들로 처리할 수 있도록 spring-jdbc 라이브러리의 sql-error-codes.xml 파일에 벤더별 에러 코드 맵핑 정보를 가지고 있다.
Spring 5.2.0 기준으로 MySQL에서 발생하는 1062 Error Code는 duplicatedKeyCodes 에 매핑되고, DB Vendor 식별에 성공한 경우 SQLErrorCodeSQLExceptionTranslator 가 sqlErrorCodes별 Exception을 던지게 된다.
sql-error-codes.xml
SQLErrorCodeSQLExceptionTranslator

실제 요청 데이터를 기반으로 Local 환경에서 재현하려했지만, DataIntegrityViolationException이 아닌 DuplicateKeyException이 발생했다.

DB Maintenance를 한 지 얼마 되지 않은 시점의 이슈레이징 이었기에, 혹시 AWS Aurora MySQL 버전 업으로 인해 Duplicate Entry의 Error Code가 변경된 것은 아닌지 확인해봤다.
Duplicate Entry

DB의 문제는 아니었다.

이후 원인을 유추할 수 없어서 로그를 다시 꼼꼼히 보다가, 스택 트레이스에서 이상한 점을 발견했다.
DB 에러를 DataAccessException 서브 클래스로 처리해주는 SQLExceptionTranslator의 구현체가 SQLErrorCodeSQLExceptionTranslator가 아닌, DB Vendor를 식별할 수 없는 경우에 동작하는 SQLExceptionSubclassTranslator였던 것이다.
SQLExceptionSubclassTranslator

Spring은 DB Vendor를 식별하기 위해 JdbcUtils#extractDatabaseMetaData 메서드를 호출한다.
해당 메서드는 DataSource를 사용하여 Connection을 만든 뒤 DB Vendor를 확인하는데, 만약 Connection 생성이 실패한 경우 SQLErrorCodesFactory는 초기화를 재시도하지 않고 런타임 중에 계속해서 SQLExceptionSubclassTranslator를 사용하게 되는 문제가 있었다.
그래서 평상시에는 문제가 없다가, 간헐적으로 문제가 발생했던 것이다.

해당 문제는 spring-framework #23675, spring-framework #25681 에서 이슈레이징 되었고, 5.2.9.RELEASE 에 반영되었다.
버그 픽스된 코드가 궁금하면 commit에서 확인할 수 있다.

결론: SQLErrorCodesFactory에 대한 재 초기화를 시도하지 않는 Spring Framework의 버그였고, fix된 Spring Framework 5.2.9 이상 버전으로 업그레이드

profile
소프트웨어 엔지니어

0개의 댓글