SpringBoot中使用Jackson导致Long型数据精度丢失返回错误

教程分享 > Java教程 > Spring (1966) 2024-08-07 11:05:25
数据库中有一个bigint类型数据(mybatis plus自增长的id),对应java后台类型为Long型,在某个查询页面中碰到了问题:页面上显示的数据和数据库中的数据不一致。例如数据库中存储的是:1475797674679549851,显示出来却成了1475797674679550000,后面几位全变成了0,精度丢失了。

1. 原因

这是因为Javascript中数字的精度是有限的,bigint类型的的数字超出了Javascript的处理范围。JS 遵循 IEEE 754 规范,采用双精度存储(double precision),占用 64 bit。

各位的含义如下:

  • 1位(s) 用来表示符号位
  • 11位(e) 用来表示指数
  • 52位(f) 表示尾数

尾数位最大是 52 位,因此 JS 中能精准表示的最大整数是 Math.pow(2, 53),十进制即 9007199254740992。而Bigint类型的有效位数是63位(扣除一位符号位),其最大值为:Math.pow(2,63)。任何大于 9007199254740992 的就可能会丢失精度:

19007199254740992     >> 10000000000000...000 // 共计 53 个 0
29007199254740992 + 1 >> 10000000000000...001 // 中间 52 个 0
39007199254740992 + 2 >> 10000000000000...010 // 中间 51 个 0

实际上值却是:

19007199254740992 + 1 // 丢失
29007199254740992 + 2 // 未丢失
39007199254740992 + 3 // 丢失
49007199254740992 + 4 // 未丢失

2.解决方法

解决办法就是让Javascript把数字当成字符串进行处理。对Javascript来说,不进行运算,数字和字符串处理起来没有什么区别。当然如果需要进行运算,只能采用其他方法,例如使用JavaScript的一些开源库bignumber之类的处理了。Java进行JSON处理的时候是能够正确处理long型的,只需要将数字转化成字符串就可以了。例如:

{
    ...
    "bankcardHash": 1475797674679549851,
    ...
}

变为:

{
    ...
    "bankcardHash": "1475797674679549851",
    ...
}

这样Javascript就可以按照字符串方式处理,不存在数字精度丢失了。在Springboot中处理方法基本上有以下几种:

2.1 配置参数write_numbers_as_strings

Jackson有个配置参数WRITE_NUMBERS_AS_STRINGS,可以强制将所有数字全部转成字符串输出。其功能介绍为:Feature that forces all Java numbers to be written as JSON strings.。使用方法很简单,只需要配置参数即可:
 

spring:
  jackson:
    generator:
      write_numbers_as_strings: true

这种方式的优点是使用方便,不需要调整代码;缺点是颗粒度太大,所有的数字都被转成字符串输出了,包括按照timestamp格式输出的时间也是如此。

2.2 注解

另一个方式是使用注解JsonSerialize

   @JsonSerialize(using=ToStringSerializer.class)
   private Long bankcardHash;

指定了ToStringSerializer进行序列化,将数字编码成字符串格式。这种方式的优点是颗粒度可以很精细;缺点同样是太精细,如果需要调整的字段比较多会比较麻烦。

2.3 自定义ObjectMapper

最后想到可以单独根据类型进行设置,只对Long型数据进行处理,转换成字符串,而对其他类型的数字不做处理。Jackson提供了这种支持。方法是对ObjectMapper进行定制。根据SpringBoot的官方帮助(https://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-mvc.html#howto-customize-the-jackson-objectmapper),找到一种相对简单的方法,只对ObjectMapper进行定制,而不是完全从头定制,方法如下:

 @Bean("jackson2ObjectMapperBuilderCustomizer")
 public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
     Jackson2ObjectMapperBuilderCustomizer customizer = new Jackson2ObjectMapperBuilderCustomizer() {
         @Override
         public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
             jacksonObjectMapperBuilder.serializerByType(Long.class, ToStringSerializer.instance)
                     .serializerByType(Long.TYPE, ToStringSerializer.instance);
         }
     };
    return customizer;
}

通过定义Jackson2ObjectMapperBuilderCustomizer,对Jackson2ObjectMapperBuilder对象进行定制,对Long型数据进行了定制,使用ToStringSerializer来进行序列化。问题终于完美解决。

附录、参考资料


 

https://www.leftso.com/article/933.html

相关文章
数据库中有一个bigint类型数据(mybatis plus自增长的id),对应java后台类型为Long型,在某个查询页面中碰到了问题:页面上显示的数据和数据库中的数据不一致
一、SpringBoot默认的错误处理机制1)浏览器,返回一个默认的错误页面2)如果是其他客户端(app),默认响应一个json数据(postman模拟)客户端
从Spring 6和Spring Boot 3开始,Spring框架支持“HTTP API的问题详细信息”规范RFC 7807。本Spring Boot 教程将详细指导您完成这一新增强。1.问题...
       ​Spring Boot       这里主要对Spring Boot 项目和Spring MVC 相关项目中,日期参数的使用以及Ajax请求日期数据返回格式的处理
为了方便管理,项目中使用统一风格的返回格式$title(Result.java) import java.io.Serializable; import java.math.BigDecima...
前言spring boot 项目常用的几个类设计,方便快速搭建项目错误处理模块。代码片段 错误枚举定义@Getterpublic enum ErrorCodeE
spring boot mybatis 整合使用讲解介绍,spring boot与MyBatis的使用讲解介绍。spring boot mybatis xml mapper方式的入门和通过一个简...
Spring boot 参数分组校验项目源码下载:demo-boot-group-validation.zip​​​​​​​ 访问密码:9987分组校验演示项目结构演示项目创建maven主要依赖...
Spring Boot 1.x升级到Spring Boot 2.0迁移指南
spring boot框架整合MyBatis数据库暂时选用MySQL
从Spring 6和Spring Boot 3开始,Spring framework支持将远程HTTP服务代理为带有HTTP交换注解方法的Java接口。类似的库,如OpenFeign和Retro...
       习使用嵌入式ActiveMQ配置Spring Boot应用程序,以便在JMSTemplate 的帮助下发送和接收JMS消息
在这篇文章中,我们将讨论有关使用异步任务执行程序功能在不同线程中执行任务的Spring boot异步执行支持。我们将看看在Spring项目中配置SimpleAsyncTaskExecutor,C...
前言在这个Spring HATEOAS示例中,我们将学习如何将HATEOAS链接添加到在spring boot项目中创建的现有REST API