一、现象
SpringBoot 项目,数据库主键使用bigint存储雪花算法生成 ID,Java 实体类使用Long接收数据库字段。后端 debug 查看 JSON 响应数据 ID 是正确的,但是前端拿到数据后 ID 后几位变成 0,出现精度丢失。
数据库表:
CREATE TABLE `user` (
`id` bigint(32) NOT NULL COMMENT '用户id',
...
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='用户表';
Java实体:
import lombok.Data;
@Data
public class User {
/**
* 用户id
*/
private Long id;
//其他成员变量省略
}
示例:后端真实 ID:1352213368413982722,前端接收后变成:1352213368413982700。
二、问题根本原因
JS 的Number类型最大安全整数为 2⁵³‑1,只能保证16 位数字精度;雪花算法生成的 ID 是 19 位数字。
后端返回 JSON,前端 JSON 字符串转 JS 对象时,19 位的 Long ID 会被解析为 JS Number,超出安全数字范围,高位保留、低位直接失真,后几位变成 0。
三、解决思路
方案 1:数据库 id 字段改成 String
直接把数据库bigint修改为字符串类型。但是字符串做主键,索引查询性能会下降,一般不推荐。
方案 2:数据库、后端保持 Long/BigInt,序列化输出给前端转为 String
数据库依旧 bigint、Java 实体依旧 Long,只在返回 JSON 给前端时把 ID 序列化为字符串,前端拿到字符串就不会触发 Number 精度丢失。有两种实现方式:
方式 A:注解(局部生效,只作用于某个字段)
在实体的 id 字段上加@JsonFormat(shape = JsonFormat.Shape.STRING),只对该字段序列化转为字符串。
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
@Data
public class User {
/**
* 用户id
*/
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long id;
//其他成员变量省略
}
方式 B:Jackson 全局配置
写配置类,自定义ObjectMapper,注册序列化器,全局所有Long类型序列化输出为字符串,不需要每个实体都加注解。
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
/**
* Jackson全局序列化配置
* 解决雪花算法Long类型ID返回前端JS精度丢失问题
* 将所有Long类型序列化为字符串返回
*/
@Configuration
public class JacksonConfig {
/**
* 自定义ObjectMapper对象,替换SpringBoot默认的Jackson序列化实例
* @Primary 优先使用本Bean,覆盖系统默认ObjectMapper
* @ConditionalOnMissingBean 容器中不存在ObjectMapper才创建,避免重复覆盖
* @param builder Jackson2ObjectMapperBuilder Spring提供的构建器
* @return ObjectMapper 自定义序列化对象
*/
@Bean
@Primary
@ConditionalOnMissingBean(ObjectMapper.class)
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
// 创建ObjectMapper实例,关闭xml映射
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
// 创建自定义模块,用于注册自定义序列化器
SimpleModule simpleModule = new SimpleModule();
// 注册序列化器:Long对象序列化为字符串
// ToStringSerializer.instance:直接调用对象toString()输出字符串
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
// 将自定义模块注册到ObjectMapper,序列化时生效
objectMapper.registerModule(simpleModule);
return objectMapper;
}
}
参考:BestEternity亲笔《解决雪花算法ID到前端之后丢失精度问题》
THE END
暂无评论内容