由Long类型引发的生产事故

简介: 由Long类型引发的生产事故

事情原由

  今天测试忽然在群里发了一个看似非常简单的线上问题,具体是:在后台通过订单编号(orderId)修改订单信息时,修改不成功 ,修改前后的订单数据完全没有发生变化。第一眼看到这个问题的时候,我心想后台实现逻辑并不就是一个updateById更新订单表的操作(简化了其他业务逻辑)吗?难道订单编号(orderId)在代码里给属性赋值赋错了,心想这么低级的错误“同事”应该不会犯吧,于是我就打开ide先去看了看对应方法的处理逻辑,整体更新操作 属性之间的赋值没有问题,难道又是一个”灵异事件“?说罢 我便想着在测试环境能不能复现一下这个bug,功能上线前功能肯定是测试通过的,于是我在测试环境点啊点,在页面上模拟了几十次更新操作也没有发现问题。

  此时我灵机一动,此次的这个问题不会和数据类型精度有什么关系吧,印象最深刻的是System.out.println(1.0F - 0.9F); 实际输出不是 0.1,难道订单号用的数据类型也存在精度丢失的问题吗?

  然后我便让测试把那条有问题的订单号发给我,终于功夫不负有心人,通过相同的数据完美的复现了bug(解决了一半)。

问题复现过程

为了简化繁杂的业务流程,这里就不在数据库建表了。只模拟问题的重点

订单实体类:

@Data
public class Order {
    private Long id;
    private Long orderId;
    private String creteName;
}
@RequestMapping("/order")
@RestController
public class OrderController {
    @GetMapping(value = "/get")
    public Order get(){
        Order order = new Order();
        order.setId(1L);
        order.setOrderId(362909601374617692L);
        order.setCreteName("张三");
        return order;
    }
}

用接口测试工具测试:

这么乍一看,返回的编号没问题,和实际代码里获取到的单号一致。此时就考虑程序员的综合开发能力了,为了模拟更加贴切真实环境,通过前端去请求获取订单信息

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
</body>
</html>
<script>
    $(function(){
        $.ajax({
            url: "http://127.0.0.1:8080/order/get",
            success: function(data){
                $("body").text(JSON.stringify(data));
            }
        })
    })
</script>

这时问题就完美的复现了,代码里返回的orderId是362909601374617692,但前端通过请求获取到的却是:362909601374617660,orderId不一致。看到这里大概就明白了,问题的原因大概是:前端的数据类型(存在精度问题)或者是http协议造成的。然后我就去查阅相关资料,最后确定原因是 :Java服务端如果直接返回Long整形数据给前端,JS会自动转换为Number类型,JS中Number 类型有些数值会有精度损失。具体原因放在最后说明,先说解决办法:既然Number类型有精度损失的问题,那我返回的时候换一个数据类型不就避免了这个问题。

解决方法

@RequestMapping("/order")
@RestController
public class OrderController {
    @GetMapping(value = "/get")
    public Order get(){
        Order order = new Order();
        order.setId(1L);
        order.setOrderId("362909601374617692");
        order.setCreteName("张三");
        return order;
    }
}

由于JS是弱类型语言,前端拿到orderId也没进行任何计算操作,所以只修改后台数据类型就可以,前端不需要修改任何代码。

原因

 Java 服务端如果直接返回 Long 整型数据给前端,JS 会自动转换为 Number 类型(注:此类型为双精度浮点数,表示原理与取值范围等同于 Java 中的 Double)。Long 类型能表示的最大值是 2 的 63 次方-1,在取值范围之内,超过 2 的 53 次方 (9007199254740992)的数值转化为 JS 的 Number 时,有些数值会有精度损失

扩展说明:在 Long 取值范围内,任何 2 的指数次整数都是绝对不会存在精度损失的,所以说精度损失是一个概率问题。若浮点数尾数位与指数位空间不限,则可以精确表示任何整数,但很不幸,双精度浮点数的尾数位只有 52 位

2 的 63 次方-1 等于 9223372036854775807

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number

总结

  本次问题主要是后端返回的订单编号是Long类型,在特定数值下会造成和前端拿到的orderId不一致,通过orderId再去更新时导致页面上显示的数据没有发生变化,有可能拿着不对的orderId更新到了其他不相关的数据。修改后采用"String"类型传递 orderId可以避免这个问题。实际开发中操作订单状态应该是通过PRIMARY KEY来操作订单表,PRIMARY KEY可以是自增id 雪花id uuid等分布式唯一id,orderId是单独的一列 非主键存储,尽量避免通过orderId操作订单数据。

至于这个问题有没有发生过,我也忘了,不过它确实是存在的。

相关文章
|
1月前
|
JSON JavaScript 前端开发
解决js中Long类型数据在请求与响应过程精度丢失问题(springboot项目中)
解决js中Long类型数据在请求与响应过程精度丢失问题(springboot项目中)
42 0
|
1月前
|
编译器 C语言
c语言中long的作用类型
c语言中long的作用类型
28 0
|
6月前
|
Java
【面试题精讲】Java超过long类型的数据如何表示
【面试题精讲】Java超过long类型的数据如何表示
|
1月前
|
存储
TS 自定义结构Long与number类型相互转换
TS 自定义结构Long与number类型相互转换
|
2月前
|
Oracle 关系型数据库 数据库
Flink Sink to Oracle 存在字段CLOB类型,如何处理错误”ORA-01461: 仅能绑定要插入LONG的LONG值“
做Flink CDC同步数据过程中,目标是Oracle数据库,其中某个字段较大被设置为CLOB类型,其中会遇到异常,”ORA-01461: 仅能绑定要插入LONG的LONG值“
|
3月前
|
缓存
Long包装类型的享元模式注意事项
昨天修复订单接口的bug
30 0
|
3月前
|
存储 自然语言处理 安全
【C++11保姆级教程】空指针(nullptr),long long类型,char16_t和char32_t类型
【C++11保姆级教程】空指针(nullptr),long long类型,char16_t和char32_t类型
【C++11保姆级教程】空指针(nullptr),long long类型,char16_t和char32_t类型
|
4月前
|
JSON 前端开发 Java
JAVA后端向前端传递Long类型数据,导致数据不一致
JAVA后端向前端传递Long类型数据,导致数据不一致
94 0
|
4月前
|
数据采集 SQL DataWorks
DataWorks有什么办法将long在search中变成date类型或者是参数里面能配置时间戳吗?
DataWorks有什么办法将long在search中变成date类型或者是参数里面能配置时间戳吗?
44 0
|
6月前
|
JSON 数据格式
Gson转换json数据为HashMap时long类型变为double问题解决
今天再使用Gson处理json数据的时候碰到了一个问题 , 当我使用如下代码解析json数据的时候 , requestTime就变为了double类型
123 0