• 由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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    @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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    用接口测试工具测试:

    在这里插入图片描述

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

    DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Documenttitle>
        <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在这里插入图片描述

    在这里插入图片描述

    这时问题就完美的复现了,代码里返回的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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

    由于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操作订单数据。

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

  • 相关阅读:
    考华为HCIP证书多钱?
    Tomcat底层原理
    数据结构 --- JAVA版链表
    实战级详解Spring框架中引入阿里开源组件Nacos作配置中心
    【Scan Kit】集成扫码服务时Android Studio总是报错OOM如何解决?
    了解被测系统(二)接入链路--包括域名解析和Nginx代理
    点与多边形关系
    vue3 + element plus 使用字节跳动图标
    @JSONField注解
    看到我们的IDE插件代码被友商复制粘贴了,所以我们做了一个愉快的决定
  • 原文地址:https://blog.csdn.net/weixin_43847283/article/details/133548807