前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >由Long类型引发的生产事故

由Long类型引发的生产事故

原创
作者头像
鱼找水需要时间
发布2023-08-31 21:44:25
1610
发布2023-08-31 21:44:25
举报
文章被收录于专栏:SpringBoot教程SpringBoot教程

事情原由

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

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

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

问题复现过程

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

订单实体类:

代码语言:java
复制
@Data
public class Order {
    private Long id;

    private Long orderId;

    private String creteName;
}
代码语言:java
复制
@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;
    }
}

用接口测试工具测试:

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

代码语言:html
复制
<!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协议造成的。然后我就去查阅相关资料,最后确定原因是 :<font color=red>Java服务端如果直接返回Long整形数据给前端,JS会自动转换为Number类型,JS中Number 类型有些数值会有精度损失</font>。具体原因放在最后说明,先说解决办法:既然Number类型有精度损失的问题,那我返回的时候换一个数据类型不就避免了这个问题。

解决方法

代码语言:java
复制
@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操作订单数据。

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 事情原由
  • 问题复现过程
  • 解决方法
  • 原因
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com