SpringBoot主鍵ID傳到前端后精度丟失的問(wèn)題解決
簡(jiǎn)介
本文用示例介紹SpringBoot如何解決雪花算法主鍵ID傳到前端后精度丟失問(wèn)題。
問(wèn)題描述
Java后端Long類型的范圍
-2^63~2^63,即:-9223372036854775808~9223372036854775807,它是19位的。
這個(gè)數(shù)字可以通過(guò)方法獲得:Long.MAX_VALUE、Long_MIN_VALUE。
前端JS的數(shù)字類型的范圍
-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)
主鍵類型是BIGINT,存儲(chǔ)雪花算法生成的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 類型對(duì)應(yīng)數(shù)據(jù)庫(kù)ID的BIGINT類型。
這里使用 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è)試
訪問(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類型的ID轉(zhuǎn)為L(zhǎng)ong類型,不會(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 類型改成 String 類型。
2.前端用String類型來(lái)保存ID保持精度,后端及數(shù)據(jù)庫(kù)繼續(xù)使用Long(BigINT)類型
方案1使用String 類型做數(shù)據(jù)庫(kù)ID,查詢性能會(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類型 simpleModule.addSerializer(Long.class, ToStringSerializer.instance); simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance); objectMapper.registerModule(simpleModule); return objectMapper; } }
測(cè)試
訪問(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類型 simpleModule.addSerializer(Long.class, BigNumberSerializer.instance); simpleModule.addSerializer(Long.TYPE, BigNumberSerializer.instance); objectMapper.registerModule(simpleModule); return objectMapper; } }
測(cè)試
訪問(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è)試
訪問(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)文章
Failed to execute goal org...的解決辦法
這篇文章主要介紹了Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1的解決辦法的相關(guān)資料,需要的朋友可以參考下2017-06-06SpringBoot自定義注解使用讀寫(xiě)分離Mysql數(shù)據(jù)庫(kù)的實(shí)例教程
這篇文章主要給大家介紹了關(guān)于SpringBoot自定義注解使用讀寫(xiě)分離Mysql數(shù)據(jù)庫(kù)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11java使用JMF實(shí)現(xiàn)音樂(lè)播放功能
這篇文章主要為大家詳細(xì)介紹了java使用JMF實(shí)現(xiàn)音樂(lè)播放的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06Java實(shí)現(xiàn)統(tǒng)計(jì)文檔中關(guān)鍵字出現(xiàn)的次數(shù)
這篇文章主要為大家分享了利用Java語(yǔ)言實(shí)現(xiàn)統(tǒng)計(jì)關(guān)鍵字在文檔中出現(xiàn)的次數(shù)的方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2022-05-05