SpringBoot主鍵ID傳到前端后精度丟失的問(wèn)題解決
簡(jiǎn)介
本文用示例介紹SpringBoot如何解決雪花算法主鍵ID傳到前端后精度丟失問(wèn)題。
問(wèn)題描述
Java后端Long類(lèi)型的范圍
-2^63~2^63,即:-9223372036854775808~9223372036854775807,它是19位的。
這個(gè)數(shù)字可以通過(guò)方法獲得:Long.MAX_VALUE、Long_MIN_VALUE。
前端JS的數(shù)字類(lèi)型的范圍
-2^53~2^53,即:-9007199254740991~9007199254740991,它是16位的。
這個(gè)數(shù)字可以通過(guò)方法獲得:Number.MAX_SAFE_INTEGER、Number.MIN_SAFE_INTEGER。
結(jié)論
可見(jiàn),Java后端的Long寬度大于前端的。雪花算法一般會(huì)生成18位或者19位寬度的數(shù)字,那么這時(shí)就會(huì)出問(wèn)題。
項(xiàng)目場(chǎng)景
1.表結(jié)構(gòu)
主鍵類(lèi)型是BIGINT,存儲(chǔ)雪花算法生成的ID。
CREATE TABLE `user` ( `id` BIGINT(32) NOT NULL COMMENT '用戶(hù)id', ... PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用戶(hù)表';
2.Entity
用Long 類(lèi)型對(duì)應(yīng)數(shù)據(jù)庫(kù)ID的BIGINT類(lèi)型。
這里使用 MybatisPlus 的雪花算法自動(dòng)生成19位長(zhǎng)度的純數(shù)字作為主鍵ID。(當(dāng)然也可以手動(dòng)用雪花算法生成ID)
import lombok.Data;
@Data
public class User {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
//其他成員
}3.響應(yīng)給前端
以JSON數(shù)據(jù)響應(yīng)給前端正常
{
"id": 1352166380631257089,
...
}問(wèn)題描述
實(shí)例
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;
}測(cè)試
訪(fǎng)問(wèn):http://localhost:8080/user/find?id=1352213368413982722
結(jié)果

問(wèn)題復(fù)現(xiàn)
從上邊可以看到,并沒(méi)有問(wèn)題。
為什么沒(méi)有出問(wèn)題?
前端傳入后端:SpingMVC會(huì)自動(dòng)將String類(lèi)型的ID轉(zhuǎn)為L(zhǎng)ong類(lèi)型,不會(huì)出問(wèn)題
后端響應(yīng)給前端:是JSON格式,與JS沒(méi)有關(guān)系,不會(huì)出問(wèn)題
什么時(shí)候會(huì)出問(wèn)題?
前端接收到JSON之后,將其序列化為JS對(duì)象,然后進(jìn)行其他操作。在JSON轉(zhuǎn)JS對(duì)象時(shí)就會(huì)出問(wèn)題,如下:

可以看到,原來(lái)id為1352213368413982722,序列化為JS對(duì)象后變成了 1352213368413982700
代碼為:
const json = '{"id": 1352213368413982722, "name": "Tony"}';
const obj = JSON.parse(json);
console.log(obj.id);
console.log(obj.name);解決方案
有如下兩種方案
1.將數(shù)據(jù)庫(kù)表設(shè)計(jì)的id字段由 Long 類(lèi)型改成 String 類(lèi)型。
2.前端用String類(lèi)型來(lái)保存ID保持精度,后端及數(shù)據(jù)庫(kù)繼續(xù)使用Long(BigINT)類(lèi)型
方案1使用String 類(lèi)型做數(shù)據(jù)庫(kù)ID,查詢(xún)性能會(huì)大幅度下降。所以應(yīng)該采用方案2。本文介紹方案2。
全局處理
簡(jiǎn)介
自定義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來(lái)序列化Long類(lèi)型
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
objectMapper.registerModule(simpleModule);
return objectMapper;
}
}測(cè)試
訪(fǎng)問(wèn):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 {
/**
* 根據(jù) JS Number.MAX_SAFE_INTEGER 與 Number.MIN_SAFE_INTEGER 得來(lái)
*/
private static final long MAX_SAFE_INTEGER = 9007199254740991L;
private static final long MIN_SAFE_INTEGER = -9007199254740991L;
/**
* 提供實(shí)例
*/
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();
// 將使用自定義序列化器來(lái)序列化Long類(lèi)型
simpleModule.addSerializer(Long.class, BigNumberSerializer.instance);
simpleModule.addSerializer(Long.TYPE, BigNumberSerializer.instance);
objectMapper.registerModule(simpleModule);
return objectMapper;
}
}測(cè)試
訪(fǎng)問(wèn):http://localhost:8080/user/find?id=1352213368413982722

局部處理
說(shuō)明
在字段上加:@JsonSerialize(using= ToStringSerializer.class)。
實(shí)例
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;
}測(cè)試
訪(fǎng)問(wèn):http://localhost:8080/user/find?id=1352213368413982722

到此這篇關(guān)于SpringBoot主鍵ID傳到前端后精度丟失的問(wèn)題解決的文章就介紹到這了,更多相關(guān)SpringBoot主鍵ID精度丟失內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Boot命令行啟動(dòng)添加參數(shù)的三種方式
在命令行中,常見(jiàn)的參數(shù)可以分為三類(lèi):選項(xiàng)參數(shù)、非選項(xiàng)參數(shù)和系統(tǒng)參數(shù),本文就來(lái)介紹一下Spring Boot命令行三種參數(shù)形式,感興趣的可以了解一下2023-09-09
Nacos后臺(tái)頻繁打印get changedGroupKeys:[]的問(wèn)題及解決
這篇文章主要介紹了Nacos后臺(tái)頻繁打印get changedGroupKeys:[]的問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01
SpringBoot 自動(dòng)掃描第三方包及spring.factories失效的問(wèn)題解決
這篇文章主要介紹了SpringBoot 自動(dòng)掃描第三方包及spring.factories失效的問(wèn)題,本文給大家分享最新解決方法,需要的朋友可以參考下2023-05-05
Java分支結(jié)構(gòu)和循環(huán)結(jié)構(gòu)原理與用法詳解
這篇文章主要介紹了Java分支結(jié)構(gòu)和循環(huán)結(jié)構(gòu)原理與用法,結(jié)合實(shí)例形式分析了java分支結(jié)構(gòu)、循環(huán)結(jié)構(gòu)、跳轉(zhuǎn)語(yǔ)句等相關(guān)概念、原理、使用技巧與操作注意事項(xiàng),需要的朋友可以參考下2020-02-02
關(guān)于SpringGateway調(diào)用服務(wù) 接受不到參數(shù)問(wèn)題
這篇文章主要介紹了關(guān)于SpringGateway調(diào)用服務(wù)接受不到參數(shù)問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12
Springboot整合camunda+mysql的集成流程分析
本文介紹基于mysql數(shù)據(jù)庫(kù),如何實(shí)現(xiàn)camunda與springboot的集成,如何實(shí)現(xiàn)基于springboot運(yùn)行camunda開(kāi)源流程引擎,本文分步驟圖文相結(jié)合給大家介紹的非常詳細(xì),需要的朋友參考下吧2021-06-06
java static塊和構(gòu)造函數(shù)的實(shí)例詳解
這篇文章主要介紹了java static塊和構(gòu)造函數(shù)的實(shí)例詳解的相關(guān)資料,希望通過(guò)本文能幫助到大家,讓大家理解掌握J(rèn)ava static關(guān)鍵字的函數(shù)方法,需要的朋友可以參考下2017-09-09
Springboot+redis+Interceptor+自定義annotation實(shí)現(xiàn)接口自動(dòng)冪等
本篇文章給大家介紹了使用springboot和攔截器、redis來(lái)優(yōu)雅的實(shí)現(xiàn)接口冪等,對(duì)于冪等在實(shí)際的開(kāi)發(fā)過(guò)程中是十分重要的,因?yàn)橐粋€(gè)接口可能會(huì)被無(wú)數(shù)的客戶(hù)端調(diào)用,如何保證其不影響后臺(tái)的業(yè)務(wù)處理,如何保證其只影響數(shù)據(jù)一次是非常重要的,感興趣的朋友跟隨小編一起看看吧2019-07-07

