에러 기록

에러 기록 - 'NaN' is not a valid numeric or approximate numeric value

uhyvn 2024. 12. 18. 23:37

서비스 막바지 개발 중 테스트 시에 어이없는 에러가 터졌다.

2024-12-18T22:56:05.609+09:00  WARN 4860 --- [nio-8084-exec-6] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 0, SQLState: S1009
2024-12-18T22:56:05.609+09:00 ERROR 4860 --- [nio-8084-exec-6] o.h.engine.jdbc.spi.SqlExceptionHelper   : 'NaN' is not a valid numeric or approximate numeric value
2024-12-18T22:56:05.627+09:00  INFO 4860 --- [nio-8084-exec-6] p6spy                                    : 
2024-12-18T22:56:05.636+09:00 ERROR 4860 --- [nio-8084-exec-6] c.j.j.c.e.h.GlobalExceptionHandler       : RuntimeException : Unable to bind parameter #9 - NaN ['NaN' is not a valid numeric or approximate numeric value] [n/a]

 

OD로 작성된 시음노트에 대한 값이 삭제되려고 할 때 발생하는 에러였기 때문에,

// 우리 서비스는 쉽게 말해 우리가 갖고 있는 데이터를 활용한 게시글과 사용자가 직접 입력한 데이터를 활용한 게시글 2개로 나뉜다.

 

실제 삭제 로직을 보면 아래와 같아서 수정할 부분이 명확했다.

	@Transactional
	public DeleteTastingNoteResponse deleteTastingNote(Member member, Long tastingNoteId) {
		TastingNote tastingNote = getTastingNote(tastingNoteId);
		validateTastingNoteWriter(member, tastingNote);

		if (!Objects.isNull(tastingNote.getAlcoholicDrinks())) {
			AlcoholicDrinks alcoholicDrinks = alcoholicDrinksReader.getById(tastingNote.getAlcoholicDrinks().getId());
			alcoholicDrinks.removeRating(tastingNote.getRating());
		}

		tastingNote.delete();
		return new DeleteTastingNoteResponse(tastingNote.getId());
	}

 

물론 우리 서비스 코드를 처음 보는 사람들은 모르겠지만..

if문 안에 있는 removeRating이 문제였다.

 

 

해당 서비스 로직을 보면,

    public void removeRating(Double existingRating) {
        double currentRating = this.rating;
        int count = this.tastingNoteCount;
        double totalScore = count * currentRating - existingRating;
        this.tastingNoteCount = count - 1;
        this.rating = totalScore / this.tastingNoteCount;
    }

 

위 로직은 alcoholicDrinks 엔티티 안에 있는 평점 업데이트 메소드이다.

해당 alcoholicDrinks로 작성된 게시글이라면, 우리는 각 alcoholicDrinks마다 평점이 있기 때문에 이렇게 평점을 업데이트 해줘야 한다.

그런데 여기서 에러가 발생한 이유는 뭘까.

 

 

문제는 tastingNoteCount가 0일 때다.

this.rating = totalScore / this.tastingNoteCount;

여기서 this.tastingNoteCount가 0이면, 0으로 나누기가 발생해 NaN이 저장된다.

 

아주 간단한 에러였다 나름..?

 

그래서 아래와 같이 방어 로직을 추가했다.

    public void removeRating(Double existingRating) {
        double currentRating = this.rating;
        int count = this.tastingNoteCount;

        // 방어 로직 추가
        if (count <= 1) {
            this.rating = 0.0; // 기본값으로 초기화
            this.tastingNoteCount = 0;
            return;
        }

        double totalScore = count * currentRating - existingRating;
        this.tastingNoteCount = count - 1;
        this.rating = totalScore / this.tastingNoteCount;
    }

 

만약 tastingNoteCount가 1보다 작거나 같다면, 현재 데이터의 rating과 tastingNoteCount를 0(기본값)으로 초기화한다.

NaN이 저장되는 것을 방지하기 위해서다.

 

 

 

 

 

 

 에러 해결!!

 

추가 정보

NaN은 "Not a Number"의 약자로, 숫자 연산에서 발생할 수 있는 특수한 상태입니다. 
Java에서 NaN은 double이나 float 타입의 값으로, 특정 연산 결과가 수학적으로 정의되지 않을 때 나타납니다.

 

NaN은 다음과 같은 상황에서 발생한다.

1. 0을 0으로 나눌 때

double result = 0.0 / 0.0; // NaN

2. 음수의 제곱근을 계산할 때

double result = Math.sqrt(-1); // NaN

3. NaN 값과 연산을 할 때

double result = Double.NaN + 1; // 여전히 NaN

 

 

 

이제 NaN이랑은 다시 만나고 싶지 않다..!