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

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

简介

本文用示例介绍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精度丢失内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • SpringBoot全局配置long转String丢失精度的问题解决

    目录 第一种方式 第二种方式 第三种方式 第四种方式(缺点:将所有的数字类型都会转为字符串) web项目中,Java后端传过来的Long/long类型,前端JS接收会丢失精度. 本文推荐第三.第四种方式 第一种方式 简单粗暴,将所有的Lang类型,改为String,数据库改成varchar类型: 第二种方式 自己建个配置类 extends WebMvcConfigurerAdapter 已经被弃用,直接实现WebMvcConfigurer该接口就行了 @EnableWebMvc @Configu

  • SpringBoot全局配置long转String丢失精度问题解决方案

    web项目中,前端JS使用Java后端传过来的Long/long会丢失精度. 后端将返回的数据改为String使用即可 第一种 自己建个配置类 extends WebMvcConfigurerAdapter 已经被弃用,直接实现WebMvcConfigurer该接口就行了 @EnableWebMvc @Configuration public class WebDataConvertConfig implements WebMvcConfigurer { public void configur

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

    目录 简介 问题描述 项目场景 问题描述 实例 问题复现 解决方案 全局处理 局部处理 简介 本文用示例介绍SpringBoot如何解决雪花算法主键ID传到前端后精度丢失问题. 问题描述 Java后端Long类型的范围 -2^63~2^63,即:-9223372036854775808~9223372036854775807,它是19位的. 这个数字可以通过方法获得:Long.MAX_VALUE.Long_MIN_VALUE. 前端JS的数字类型的范围 -2^53~2^53,即:-9007199

  • SpringBoot雪花算法主键ID传到前端后精度丢失问题的解决

    目录 简介 问题描述 项目场景 问题描述 问题复现 解决方案 法1:全局处理 法2:局部处理 简介 本文用示例介绍SpringBoot如何解决雪花算法主键ID传到前端后精度丢失问题. 问题描述 Java后端Long类型的范围 -2^63~2^63,即:-9223372036854775808~9223372036854775807,它是19位的. 这个数字可以通过方法获得:Long.MAX_VALUE.Long_MIN_VALUE. 前端JS的数字类型的范围 -2^53~2^53,即:-9007

  • SpringBoot解决BigDecimal传到前端后精度丢失问题

    目录 简介 问题描述 实例 问题复现 Java后端BigDecimal的范围 解决方案 方案1:全局处理 方案2:局部处理 简介 本文用示例介绍SpringBoot如何解决BigDecimal传到前端后精度丢失问题. 问题描述 实例 Controller package com.knife.controller; import com.knife.entity.UserVO; import org.springframework.web.bind.annotation.GetMapping; i

  • Mybatis执行插入语句后并返回主键ID问题

    目录 1.MySQL数据库设置ID自增情况 2.使用UUID自增主键 3.mybatis-plus在执行插入语句后返回自定义ID 总结 我们知道JDBC可以实现插入语句后返回主键Id,那mybatis可以实现吗? 答案是肯定的. 1.MySQL数据库设置ID自增情况 <insert id="insertUser" parameterType="com.crush.mybatisplus.entity.User"> INSERT INTO tb_user

  • Mybatis批量插入返回插入成功后的主键id操作

    我们都知道Mybatis在插入单条数据的时候有两种方式返回自增主键: 1.对于支持生成自增主键的数据库:增加 useGenerateKeys和keyProperty ,<insert>标签属性. 2.不支持生成自增主键的数据库:使用<selectKey>. 但是怎么对批量插入数据返回自增主键的解决方式网上看到的还是比较少,至少百度的结果比较少. Mybatis官网资料提供如下: First, if your database supports auto-generated key

  • 详解mybatis插入数据后返回自增主键ID的问题

    1.场景介绍: ​开发过程中我们经常性的会用到许多的中间表,用于数据之间的对应和关联.这个时候我们关联最多的就是ID,我们在一张表中插入数据后级联增加到关联表中.我们熟知的mybatis在插入数据后返回的是插入成功的条数,那么这个时候我们想要得到相应的这条新增数据的ID,该怎么办呢? 2.插入数据返回自增主键ID方法(一) 在映射器中配置获取记录主键值xml映射: 在xml中定义useGeneratedKeys为true,返回主键id的值,keyProperty和keyColumn分别代表数据库

  • mybatis新增save结束后自动返回主键id详解

    目录 mybatis新增save结束后自动返回主键id 1.使用场景 2.原理在Mybatis配置了 mybatis或者mybatis-plus中save方法返回主键值 1.mapper.xml中 2.service或者dao中 mybatis新增save结束后自动返回主键id 1.使用场景 save操作之前实体类中id为null,save之后自动返回带id的实体类 @Override public ChartPagePanel save(ChartPagePanel entity) { Use

  • Mybatis-Plus默认主键策略导致自动生成19位长度主键id的坑

    某天检查一位离职同事写的代码,发现其对应表虽然设置了AUTO_INCREMENT自增,但页面新增功能生成的数据主键id很诡异,长度达到了19位,且不是从1开始递增的-- 我检查了一下,发现该表目前自增主键已经变成从1468844351843872770开始递增了-- 这就很奇怪了,目前该表数据量很少,且主键是设置AUTO_INCREMENT,正常而言,应该自增id仍在1000范围内,但目前已经变成一串长数字. 底层ORM框架用的是Mybatis-Plus,我寻思了一下,这看起来像是在插入数据库就

  • 如何用注解的方式实现Mybatis插入数据时返回自增的主键Id

    目录 用注解实现Mybatis插入数据返回自增的主键Id 设计数据库表 设计Java bean对象 添加mapper接口 Mybatis注解增(返回自增id) 删查改以及(一对一,一对多,多对多) 数据库表 目录结构 导入坐标(包) 配置文件 实体类 mapper接口编写 测试 用注解实现Mybatis插入数据返回自增的主键Id 我们在数据库表设计的时候,一般都会在表中设计一个自增的id作为表的主键.这个id也会关联到其它表的外键. 这就要求往表中插入数据时能返回表的自增id,用这个ID去给关联

  • THINKPHP在添加数据的时候获取主键id的值方法

    在使用ThinkPHP新增数据后可以很方便的获取自动增长型的主键值. $Model = D('Blog'); $data['name'] = 'test'; $data['title'] = '测试标题'; $data['content'] = '测试内容'; $result = $Model->add($data); if ($result){ $id = $result; // 获取数据库写入数据的主键 }else{ exit($Model->getError()); } 如果你的数据表主

随机推荐