SpringBoot主键ID传到前端后精度丢失的问题解决

网友投稿 509 2022-07-29


目录简介问题描述项目场景问题描述实例问题复现解决方案全局处理局部处理

简介

本文用示例介绍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.sFzfabeeIpringframework.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对象后变成了 135221336http://8413982700

代码为:

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


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Lombok 安装和使用小技巧
下一篇:Springboot整合mybatis开启二级缓存的实现示例(springboot mybatis redis二级缓存)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~