简介
本文用示例介绍SpringBoot如何解决雪花算法主键ID传到前端后精度丢失问题。
问题描述
Java后端Long类型的范围
-2^63~2^63,即:-9223372036854775808~9223372036854775807,它是19位的。
这个数字可以通过方法获得:Long.MAX_VALUE、Long_MIN_VALUE。
前端JS的数字类型的范围
-2^53~2^53,即:-9007199254740991~9007199254740991,它是16位的。
这个数字可以通过方法获得:Number.MAX_SAFE_INTEGER、Number.MIN_SAFE_INTEGER。
结论
可见,Java后端的Long宽度大于前端的。雪花算法一般会生成18位或者19位宽度的数字,那么这时就会出问题。
项目场景
1.表结构
主键类型是BIGINT,存储雪花算法生成的ID。
CREATE TABLE `user` ( `id` BIGINT(32) NOT NULL COMMENT '用户id', ... PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
2.Entity
用Long 类型对应数据库ID的BIGINT类型。
这里使用 MybatisPlus 的雪花算法自动生成19位长度的纯数字作为主键ID。(当然也可以手动用雪花算法生成ID)
import lombok.Data; @Data public class User { @TableId(type = IdType.ASSIGN_ID) private Long id; //其他成员 }
3.响应给前端
以JSON数据响应给前端正常
{ "id": 1352166380631257089, ... }
问题描述
实例
Controller
package com.knife.controller; import com.knife.entity.UserVO; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("user") public class UserController { @GetMapping("find") public UserVO find(Long id) { UserVO userVO = new UserVO(); userVO.setId(id); userVO.setUsername("Tony"); return userVO; } }
Entity
package com.knife.entity; import lombok.Data; @Data public class UserVO { private Long id; private String username; }
测试
访问:http://localhost:8080/user/find?id=1352213368413982722
结果
问题复现
从上边可以看到,并没有问题。
为什么没有出问题?
前端传入后端:SpingMVC会自动将String类型的ID转为Long类型,不会出问题
后端响应给前端:是JSON格式,与JS没有关系,不会出问题
什么时候会出问题?
前端接收到JSON之后,将其序列化为JS对象,然后进行其他操作。在JSON转JS对象时就会出问题,如下:
可以看到,原来id为1352213368413982722,序列化为JS对象后变成了 1352213368413982700
代码为:
const json = '{"id": 1352213368413982722, "name": "Tony"}'; const obj = JSON.parse(json); console.log(obj.id); console.log(obj.name);
解决方案
有如下两种方案
1.将数据库表设计的id字段由 Long 类型改成 String 类型。
2.前端用String类型来保存ID保持精度,后端及数据库继续使用Long(BigINT)类型
方案1使用String 类型做数据库ID,查询性能会大幅度下降。所以应该采用方案2。本文介绍方案2。
全局处理
简介
自定义ObjectMapper。
方案1:ToStringSerializer(推荐)
package com.knife.config; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; @Configuration public class JacksonConfig { @Bean public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) { ObjectMapper objectMapper = builder.createXmlMapper(false).build(); // 全局配置序列化返回 JSON 处理 SimpleModule simpleModule = new SimpleModule(); // 将使用String来序列化Long类型 simpleModule.addSerializer(Long.class, ToStringSerializer.instance); simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance); objectMapper.registerModule(simpleModule); return objectMapper; } }
测试
访问:http://localhost:8080/user/find?id=1352213368413982722
方案2:自定义序列化器(不推荐)
序列化器
package com.knife.config; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; import com.fasterxml.jackson.databind.ser.std.NumberSerializer; import java.io.IOException; /** * 超出 JS 最大最小值 处理 */ @JacksonStdImpl public class BigNumberSerializer extends NumberSerializer { /** * 根据 JS Number.MAX_SAFE_INTEGER 与 Number.MIN_SAFE_INTEGER 得来 */ private static final long MAX_SAFE_INTEGER = 9007199254740991L; private static final long MIN_SAFE_INTEGER = -9007199254740991L; /** * 提供实例 */ public static final BigNumberSerializer instance = new BigNumberSerializer(Number.class); public BigNumberSerializer(Class extends Number> rawType) { super(rawType); } @Override public void serialize(Number value, JsonGenerator gen, SerializerProvider provider) throws IOException { // 超出范围 序列化位字符串 if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) { super.serialize(value, gen, provider); } else { gen.writeString(value.toString()); } } }
ObjectMapper配置
package com.knife.config; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; @Configuration public class JacksonConfig { @Bean public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) { ObjectMapper objectMapper = builder.createXmlMapper(false).build(); // 全局配置序列化返回 JSON 处理 SimpleModule simpleModule = new SimpleModule(); // 将使用自定义序列化器来序列化Long类型 simpleModule.addSerializer(Long.class, BigNumberSerializer.instance); simpleModule.addSerializer(Long.TYPE, BigNumberSerializer.instance); objectMapper.registerModule(simpleModule); return objectMapper; } }
测试
访问:http://localhost:8080/user/find?id=1352213368413982722
局部处理
说明
在字段上加:@JsonSerialize(using= ToStringSerializer.class)。
实例
package com.knife.entity; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import lombok.Data; @Data public class UserVO { @JsonSerialize(using= ToStringSerializer.class) private Long id; private String username; }
测试
访问:http://localhost:8080/user/find?id=1352213368413982722
到此这篇关于SpringBoot主键ID传到前端后精度丢失的问题解决的文章就介绍到这了,更多相关SpringBoot主键ID精度丢失内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!