주요 콘텐츠로 건너뛰기

CooLWJ 검색

选择搜索范围,输入关键词后按回车。

언어

너무 긴 것도 잘못일까?——백엔드 Long형 ID 정밀도 손실을 고치는 신기한 여정

서론

프런트엔드와 백엔드가 분리된 시대,우리의 삶은 수많은 기회와 도전으로 가득하다——갑자기 튀어나와 사람을 미치게 하는 Bug도 그중 하나다。오늘 이야기할 것은 수많은 개발자를 웃지도 울지도 못하게 한 고전적인 문제다:백엔드 Long 유형 ID가 너무 길어 프런트엔드에서 정밀도가 손실됨。이 문제를 말하자면 정말 ‘만악의 근원’이지. 하필 JavaScript 안전하게 처리할 수 있는 것은 오직 Number.MAX_SAFE_INTEGER(즉 9007199254740991) 이내의 수치는 어떨까?

이 문제 때문에 머리를 싸맨 적이 있다면 걱정하지 말자. 당신만 그런 것은 아니다. 오늘은 이 “세기의 난제”를 유머러스하게 분석할 뿐만 아니라 근원부터 해결해, 코드가 더 이상 “정밀도를 잃지” 않도록 해 보겠다.

문제의 배경:왜 Long 이렇게 길다고?

우선, 우리는 Long 유형부터 이야기해 보자.Long,바로 Java 의 64비트 정수형으로, 처리하기를 좋아하는 사람에게빅데이터、큰 수를 다루는 Java에 이 타입은 그야말로 희소식이다。하지만 프런트엔드 세계는 조금 “견문이 좁아서”,53비트 이내의 정수만 처리할 줄 안다。그렇다,잘못 들은 것이 아니다,이 점에서는 JavaScript 마치 “한 박자 느린” 고지식한 노학자처럼, 더 큰 숫자를 만나면 소수점과 지수를 이리저리 만지작거리다가 결국 절망감을 안겨 주는 숫자를 뱉어 낸다. 그러면 정밀도 손실이라는 문제가 유령처럼 프로젝트 안을 떠돌기 시작한다.

첫 만남:정밀도 손실에 얽힌 이야기

이야기는 어느 날 접수된 Bug 제보에서 시작됩니다:“개발자 오빠, 이 ID가 왜 바뀌었는지 좀 봐 주세요. 이건 제가데이터베이스안에 있는 그 ID 말이야!” 당신은 눈살을 찌푸리며 확인한다. 맞다,9223372036854775807 다음과 같이 바뀌었다 9223372036854776000,아이고, 갑자기 늘어난 이 숫자는 꼭 마술 같다. 백엔드에서 준 값은 분명 맞는데! 머리를 긁적이며 속으로 생각한다:“이건 틀림없이 프런트엔드 탓이야!”

프런트엔드 탓일까, 백엔드 탓일까?——근본 원인 추적

사실 이 문제를 프런트엔드 탓으로 돌리는 것도 전혀 근거 없는 이야기는 아니다. 한번 살펴보자 JavaScript 숫자를 처리할 때의 “약점”.JavaScript 의 Number 유형은 IEEE 754 표준에 기반한 배정밀도 부동소수점 형식으로, 53비트의 이진 숫자까지만 안전하게 표현할 수 있다. 즉 Number.MAX_SAFE_INTEGER 의 값——9007199254740991.

다시 말해, 이 범위를 벗어나는 정수는,JavaScript “정밀도가 떨어지기” 시작하고, 그 “두뇌 용량”이 갑자기 부족해집니다. 그러면 여러분의 Long 유형 ID는 그 눈에 그다지 “매력적이지 않은” 숫자 뭉치로 보이게 된다. 그렇다 보니 프런트엔드는 이렇게 터무니없이 긴 ID를 처리할 때 약간의 “희생”을 감수할 수밖에 없고, 그 결과 원래 값을 충실히 지키던 ID가 잘려 나가 “재미있는” 새 숫자로 바뀌어 버린다.

충격에서 냉정으로:해결책 찾기

문제의 근원을 찾았으니, 이제 우리 개발자들이 솜씨를 발휘할 차례다. 이어서 몇 가지 해결책을 자세히 살펴보고 각 방법의 장단점을 설명하겠다. 모든 길은 로마로 통하니 말이다.

1. 문자열로 바로 변환하기:단순하고 거칠지만 효율적

이런 문제에 직면하면,가장 먼저 떠오르는 것은 분명 가장 단순하고 직접적인 방법이다:바로 Long 타입의데이터 변환문자열로 만들면 되잖아! 어차피 JavaScript 부동소수점 수의 맹목적인 팬이라면, 아예 문제를 다시 떠넘기며 이렇게 말하자:“이건 그냥 문자열이라고 생각하면 돼, 길이가 얼마나 긴지는 신경 쓰지 마!”

어떻게 하면 될까요?사실 아주 간단합니다, 사용하는 것은 Jackson 제공한 ToStringSerializer,우리는 손쉽게 Long 문자열로 변환합니다.

import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;

public class UserDto {
    @JsonSerialize(using = ToStringSerializer.class)
    private Long id;
    // 其他字段
}


이렇게 하면 백엔드가 반환할 때 UserDto 할 때,Long 타입의 id 는 문자열로 직렬화되며, 프런트엔드에서도 문자열로 받으므로 정밀도 문제가 자연스럽게 해결됩니다. 마치 JavaScript의 머리에 “이건 문자열이야, 신경 쓰지 않아도 돼!”라는 메모를 붙여 놓은 것 같습니다.

2. 프런트엔드에서 사용 BigInt:큰 수도 정확하게 연산할 수 있도록

물론 단순하고 거친 방법이 언제나 모든 상황에 적합한 것은 아니다. 프런트엔드에서 이 ID로 어떤 수학 연산을 해야 한다고 생각해 보자. 곧바로 문자열로 바꾸는 것은 그다지 좋은 방법이 아닐 것이다. 그렇다면 프런트엔드에서는 이렇게 “매우 긴” ID를 어떻게 처리해야 할까?

다행히,JavaScript 그렇다고 쓸모가 전혀 없는 것은 아닙니다. 도입된 것은 BigInt 이후,JavaScript 마침내 소수점이나 만지작거리던 얼간이 신세에서 벗어났습니다.BigInt 임의 정밀도 정수를 처리하기 위해 마련된 새로운 원시 자료형입니다. 다음과 같이 사용할 수 있습니다:

const id = BigInt("9223372036854775807");
console.log(id + 1n); // 输出:9223372036854775808n

이렇게 하면 프런트엔드에서 정확하게 처리할 수 있습니다. Long 타입의 데이터를 다루면서 정밀도 손실 문제를 피할 수 있습니다. 물론 여기서 한 가지 짚고 넘어갈 점이 있습니다:BigInt 모든 브라우저가 지원하는 것은 아니므로, 애플리케이션 환경에서 호환되는지 반드시 확인해야 한다.

3. 사용자 정의 직렬화:복잡한 문제를 단순하게

위의 방법으로 이미 대부분의 문제를 해결할 수 있지만,때로는 더 세밀한 제어가 필요한 상황을 만나게 됩니다. 이럴 때,Jackson 의 사용자 정의 직렬화기가 제 역할을 하게 된다.

사용자 정의 직렬화기를 작성하여 필요에 따라 유연하게 제어할 수 있다 Long 타입 필드의 직렬화 과정입니다. 예를 들어 직렬화할 때 다음 항목을 처리할지 결정할 수 있습니다. Long 문자열로 변환하거나,특정 조건에 따라 다르게 처리합니다:

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

import java.io.IOException;

public class CustomLongSerializer extends JsonSerializer<Long> {

    @Override
    public void serialize(Long value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        if (value != null) {
            gen.writeString(value.toString());
        }
    }
}


그런 다음 필요한 곳에 이 사용자 정의 직렬화기를 적용한다:

import com.fasterxml.jackson.databind.annotation.JsonSerialize;

public class UserDto {
    @JsonSerialize(using = CustomLongSerializer.class)
    private Long id;
    // 其他字段
}


이렇게 하면 전체 직렬화 과정을 제어하여, 각각의 Long 모두 원하는 방식으로 처리할 수 있다. 물론 이 방법은 유연하지만 조금 더 복잡하며, 코드를 몇 줄 더 작성해야 하고 유지 관리 비용도 늘어날 수 있다.

4. 전역 처리:시간과 수고를 줄이는 방안

각 필드에 어노테이션을 설정하는 번거로움을 줄이고 이 문제를 전역적으로 해결하고 싶다면,전역 설정을 고려할 수 있다 ObjectMapper。다음 위치에서 Spring Boot 에서 전역으로 설정하는 ObjectMapper,모든 것을 Long 타입의 필드는 모두 자동으로 문자열로 변환된다.

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;

@Configuration
public class JacksonConfig {

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        SimpleModule module = new SimpleModule();
        module.addSerializer(Long.class, ToStringSerializer.instance);
        module.addSerializer(Long.TYPE, ToStringSerializer.instance);
        mapper.registerModule(module);
        return mapper;
    }
}


한 번 설정해 두면 특정 필드의 직렬화기 설정을 빠뜨릴 걱정 없이,프로젝트 전체에서 정밀도가 손실되지 않는 쾌적함을 누릴 수 있습니다. 물론 전역 설정을 사용할 때는,변환하고 싶지 않은 일부 항목에도 영향을 줄 수 있다는 점에 주의해야 합니다 Long 필드이므로, 신중히 고려해야 한다。

실제 사례:우여곡절 끝에 해결한 Bug

이 해결책들의 실제 효과를 더 잘 이해하실 수 있도록 실제 프로젝트의 이야기를 들려드리겠습니다. 이 프로젝트는 사용자 ID 관리와 관련된 것으로, 사용자 수가 매우 많아 백엔드에서는 다음을 사용했습니다 Long 유형의 고유 식별자. 프로젝트를 출시한 지 며칠 지나지 않아, 사용자에게서 “제 ID가 왜 달라졌죠?”라는 피드백이 들어왔다.

첫 번째 단계:문제의 근본 원인 추적

우리는 먼저 프런트엔드 코드를 확인했고, 다음을 발견했습니다 JavaScript 의 Number 타입은 실제로 19자리에 달하는 이 숫자를 정확히 표현할 수 없었고,그 결과 정밀도 손실이 발생했다。이어서 백엔드 코드를 살펴본 결과,비록 Long 형식의 데이터는 백엔드에서 올바르지만, REST를 통해 API 프런트엔드에 반환할 때 숫자의 정밀도가 손실되었다.

두 번째 단계:적절한 해결책 선택

문제를 신속히 해결하기 위해 우리는 우선 다음을 적용하기로 했다 @JsonSerialize(using = ToStringSerializer.class) 이 간단하고 효과적인 방법. 이 방법을 통해 프런트엔드가 정밀도가 손실된 숫자를 받는 일을 성공적으로 방지했다.

하지만 문제는 완전히 해결되지 않았다. 이후 요구 사항에서 프런트엔드는 사용자의 ID 정렬처럼 ID에 특정 연산을 수행해야 했다. 이때 문자열로는 다소 역부족이었다.

세 번째 단계:사용 BigInt 프런트엔드 연산 문제 해결

그래서 우리는 프런트엔드에 도입하기로 결정했다 BigInt。다음을 사용하면 BigInt,프런트엔드는 이처럼 매우 긴 ID를 정확하게 저장할 수 있을 뿐만 아니라 필요한 수학 연산도 수행할 수 있다. 테스트 결과 이 방법은 주요 브라우저에서 모두 잘 작동했으며, 유일한 단점은 일부 구형 브라우저에 대한 지원이 다소 부족하다는 것이다.

네 번째 단계:최종 전역 설정

앞으로 비슷한 문제가 다시 발생하지 않도록, 우리는 다음을 하기로 결정했다 ToStringSerializer 전역으로 적용되도록 설정합니다. 이렇게 하면 모든 Long 유형의 데이터는 직렬화할 때 모두 자동으로 문자열로 변환되어, 프런트엔드의 데이터 정확성을 보장하면서 코드의 중복 설정도 줄인다.

맺음말:정밀도 손실의 종결자

이 사례에서 알 수 있듯이, 비록 Long 자료형의 정밀도 손실 문제는 단순해 보이지만, 실제 프로젝트에서는 여러 잠재적 문제를 일으킬 수 있다. 여러 해결책을 비교하고 시도한 끝에 우리는 마침내 우리 프로젝트에 가장 적합한 방안을 찾았다.

이 유머러스하면서도 상세한 블로그 글이 여러분이 처리할 때 도움이 되기를 바랍니다 Long 자료형의 정밀도 손실 문제를 다룰 때 시행착오를 줄일 수 있다. 비슷한 경험이 있다면 댓글로 이야기를 나눠 주기 바란다. 여러분의 경험이 다른 개발자들에게 더 많은 영감을 줄 수도 있다.

마지막으로,기억하세요. 코드는 인생과 같아서,가끔 “잃어버리는” 일은 두렵지 않습니다. 알맞은 “직렬화기”를 찾아 정상 궤도로 되돌리는 것이 중요합니다. 여러분의 코드가 다시는 “정밀도를 잃지” 않고,정확함을 유지하며 줄곧 순조롭게 작동하기를 바랍니다!


댓글

보내는 중

원하는 테마를 선택하세요

선택한 테마가 사이트 전체에 적용됩니다.