数据库中有一个bigint类型数据,对应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 的就可能会丢失精度:

1
2
3
9007199254740992     >> 10000000000000...000 // 共计 53 个 0
9007199254740992 + 1 >> 10000000000000...001 // 中间 52 个 0
9007199254740992 + 2 >> 10000000000000...010 // 中间 51 个 0

实际上值却是:

1
2
3
4
9007199254740992 + 1 // 丢失
9007199254740992 + 2 // 未丢失
9007199254740992 + 3 // 丢失
9007199254740992 + 4 // 未丢失

2.解决方法

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

1
2
3
4
5
{
...
"bankcardHash": 1475797674679549851,
...
}

变为:

1
2
3
4
5
{
...
"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.。使用方法很简单,只需要配置参数即可:

1
2
3
4
spring:
jackson:
generator:
write_numbers_as_strings: true

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

2.2 注解

另一个方式是使用注解JsonSerialize

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

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

 实现方法:

在dto所在项目中,新建一个helper包(名字自定义,也可以放现有包里)。PS:为什么要建到dto项目中?因为,这个包最后可能会给其他组使用,这样以来,所有的处理规则逻辑都是统一的,方便对接。

        在包里添加类LongJsonSerializer,代码如下:
/**
* Long 类型字段序列化时转为字符串,避免js丢失精度
*
*/
public class LongJsonSerializer extends JsonSerializer<Long> {
@Override
public void serialize(Long value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
String text = (value == null ? null : String.valueOf(value));
if (text != null) {
jsonGenerator.writeString(text);
}
}
} 
然后在包里再添加类LongJsonDeserializer,代码如下:
/**
* 将字符串转为Long
*
*/
public class LongJsonDeserializer extends JsonDeserializer<Long> {
private static final Logger logger = LoggerFactory.getLogger(LongJsonDeserializer.class); @Override
public Long deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
String value = jsonParser.getText();
try {
return value == null ? null : Long.parseLong(value);
} catch (NumberFormatException e) {
logger.error("解析长整形错误", e);
return null;
}
}
}
好了,接下来是使用这两个类。在需要处理的id字段上,加上注解,比如如下代码:
/**
* id
*/
@JsonSerialize(using = LongJsonSerializer.class)
@JsonDeserialize(using = LongJsonDeserializer.class)
private Long id;

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进行定制,而不是完全从头定制,方法如下:

1
2
3
4
5
6
7
8
9
10
11
@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来进行序列化。问题终于完美解决。

SpringBoot中使用Jackson导致Long型数据精度丢失问题的更多相关文章

  1. springboot 解决Jackson导致Long型数据精度丢失问题

    代码中注入一个bean即可: /** * 解决Jackson导致Long型数据精度丢失问题 * * @return */ @Bean("jackson2ObjectMapperBuilder ...

  2. SpringBoot中使用Jackson将null值转化为""或者不返回的配置

    第一种方式:SpringBoot中使用Jackson将null值转化为"" 前言:在实际项目中难免会遇到null值的出现,但是我们转json时并不希望出现NULL值,而是将NULL ...

  3. springboot中关于Long类型返回前端精度丢失问题处理

    使用了HuTool这个雪花算法后,会出现丢失精度的问题 hutool算法使用地址 对于一些大的业务表,自增主键这里 接口层得注意下是否会产生大数值 设计接口的时候采用String类型. 在项目中,我们 ...

  4. 在Springboot + Mybaitis-plus 项目中利用Jackson实现json对java多态的(反)序列化

    Jackson允许配置多态类型处理,当JSON面对的转换对象是一个接口.抽象类或者一个基类的时候,可以通过一定配置实现JSON的转换.在实际项目中,Controller层接收入参以及在Dao层将对象以 ...

  5. SrpingMVC/SpringBoot中restful接口序列化json的时候使用Jackson将空字段,空字符串不传递给前端

    笔者的JSON如下: { "code": 10001, "message": "成功", "nextUrl": null ...

  6. SpringBoot中使用Fastjson/Jackson对JSON序列化格式化输出的若干问题

    来源 :https://my.oschina.net/Adven/blog/3036567 使用springboot-web编写rest接口,接口需要返回json数据,目前国内比较常用的fastjso ...

  7. SpringBoot中Jackson的过滤使用

    在接口的返回对象中,可能会有一些属性为null或者需要禁止某些字段返回给客户端. 在SpringBoot中可使用内置了Jackson实现这个需求 1. 过滤为null字段 在实体类中使用@JsonIn ...

  8. Springboot中Jackson的操作

    有一段时间没写博客了,虽然是菜鸟一枚但毕竟总要有东西记录学习的,我相信有志者事竟成.今天在工作中使用Jackson转换了一个javabean,传到测试服上之后发现日期少了一天,使用的是@JsonFor ...

  9. SpringBoot学习笔记(9)----SpringBoot中使用关系型数据库以及事务处理

    在实际的运用开发中,跟数据库之间的交互是必不可少的,SpringBoot也提供了两种跟数据库交互的方式. 1. 使用JdbcTemplate 在SpringBoot中提供了JdbcTemplate模板 ...

随机推荐

  1. SQL的Join语法

    对于SQL的Join,在学习起来可能是比较乱的.我们知道,SQL的Join语法有很多inner的,有outer的,有left的,有时候,对于Select出来的结果集是什么样子有点不是很清楚.Codin ...

  2. How do I avoid capturing self in blocks when implementing an API?

    Short answer Instead of accessing self directly, you should access it indirectly, from a reference t ...

  3. 编写高质量代码改善C#程序的157个建议——建议101:使用扩展方法,向现有类型“添加”方法

    建议101:使用扩展方法,向现有类型“添加”方法 考虑如何让一个sealed类型具备新的行为.以往我们会创建一个包装器类,然后为其添加方法,而这看上去一点儿也不优雅.我们也许会考虑修改设计,直接修改s ...

  4. 团体程序设计天梯赛L3-010 是否完全二叉搜索树 2017-03-24 16:12 29人阅读 评论(0) 收藏

    L3-010. 是否完全二叉搜索树 时间限制 400 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 陈越 将一系列给定数字顺序插入一个初始为空的二叉搜 ...

  5. modelsim使用常见问题及解决办法集锦③

    四.You selected Modelsim-Altera as Simulation Software in EDA Tool Settings,however…… You selected Mo ...

  6. Android-xx倍图

    图片文件夹对应倍图关系: ldpi 0.75 倍图 mdpi   1.0 倍图   hdpi    1.5 倍图 xhdpi  2.0 倍图 xxhdpi    3.0 倍图 xxxhdpi  4.0 ...

  7. mysql按照天或小时group分组统计

    select DATE_FORMAT( deteline, "%Y-%m-%d %H" ) , COUNT( * ) FROM test GROUP BY DATE_FORMAT( ...

  8. C# 利用CMD命令行结束进程

    public static void CmdKillProcess(int pid)        {            string cmdStr = string.Format("t ...

  9. IntelliJ OpenCV 开发环境搭建

    Windows下的IntelliJ + OpenCV开发环境搭建 基于IntelliJ IDEA 15 和 OpenCV 3.1.0 1. 在OpenCV官网下载OpenCV安装程序,双击解压到目标目 ...

  10. [python] [转]如何自动生成和安装requirements.txt依赖

    [转]如何自动生成和安装requirements.txt依赖 在查看别人的Python项目时,经常会看到一个requirements.txt文件,里面记录了当前程序的所有依赖包及其精确版本号.这个文件 ...