当 SQL Server(mssql-jdbc) 遇上 BigDecimal → 精度丢失,真坑!

Posted 青石路

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了当 SQL Server(mssql-jdbc) 遇上 BigDecimal → 精度丢失,真坑!相关的知识,希望对你有一定的参考价值。

开心一刻

  中午和哥们一起喝茶

  哥们说道:晚上喝酒去啊

  我:不去,我女朋友过生日

  哥们瞪大眼睛看着我:你有病吧,充气的过什么生日

  我生气到:有特么生产日期的好吧

需求背景

  系统对接了外部系统,调用外部系统的接口需要付费,一个接口一次调用付费 0.03 元

  同一个月内,同一个接口最高付费 25 元

  统计每个月的付费情况

  需求清楚了不?不清楚? 给大家举个案例

  这下明白了吧

  明白了需求,相信大家都会觉得很简单,不就是一个分组汇总吗?

  客官说的对,但生活总会给我们一点 surprise 

  我们慢慢往下看

环境准备

   SQL Server 版本: SQL Server 2017 

   MySQL 版本: 8.0.27 

  引入 MySQL ,是为了跟 SQL Server 做对比

   SQL Server 建表并初始化数据

CREATE TABLE tbl_interface_call_times (
    id BIGINT PRIMARY KEY IDENTITY(1,1),
    call_month INT NOT NULL,
        interface varchar(50) NOT NULL ,
        times INT NOT NULL
);
INSERT INTO tbl_interface_call_times(call_month, interface, times) VALUES
(202301, \'interface1\', 800),
(202301, \'interface2\', 1000),
(202301, \'interface3\', 100),
(202302, \'interface1\', 833),
(202302, \'interface2\', 834),
(202302, \'interface3\', 134),
(202302, \'interface4\', 243),
(202302, \'interface5\', 2143);

   MySQL 建表并初始化数据

CREATE TABLE tbl_interface_call_times (
    id INT UNSIGNED NOT NULL AUTO_INCREMENT,
    call_month INT NOT NULL COMMENT \'月份\',
        interface varchar(50) NOT NULL COMMENT \'接口\',
        times INT NOT NULL COMMENT \'调用次数\',
    PRIMARY KEY(id)
) COMMENT \'接口调用次数\';
INSERT INTO tbl_interface_call_times(call_month, interface, times) VALUES
(202301, \'interface1\', 800),
(202301, \'interface2\', 1000),
(202301, \'interface3\', 100),
(202302, \'interface1\', 833),
(202302, \'interface2\', 834),
(202302, \'interface3\', 134),
(202302, \'interface4\', 243),
(202302, \'interface5\', 2143);

  汇总每个月的付费, SQL 该如何写?

  很简单的啦,如下所示

SELECT call_month, 
    SUM(
        CASE WHEN times * 0.03 > 25 THEN 25
        ELSE times * 0.03
        END
    ) monthFee
FROM tbl_interface_call_times
GROUP BY call_month

  通用写法, SQL Server 和 MySQL 都支持

  我们看下查询结果

  一切都很正常,觉得世界真美好!

问题复现

  我们不能光玩数据库吧?

  不得像这样雨露均沾?

  必须把 spring-boot 、 MyBatis-Plus 安排上

   mysql-jdbc 版本: 8.0.21 , mssql-jdbc 版本: 6.2.1.jre8 

  完整代码:mybatis-plus-dynamic-datasource

  访问: http://localhost:8081/interface/summary?startMonth=202301&endMonth=202302 

  你会发现,你心心念念的 surprise 终于出现了!

  正确应该是 86.3.3 哪去了?

  直查数据库是没问题的呀

  莫非 MyBatis-Plus 有问题?

  我们切到 MySQL 试试;将 InterfaceCallTimesServiceImpl 上的数据源改成 mysql_db 

  然后重启,我们再访问: http://localhost:8081/interface/summary?startMonth=202301&endMonth=202302 

  这说明应该不是 MyBatis 的问题,那不完犊子了?

问题解决

  是不是束手无策了? 也不是,我们可以 Bing 一下的嘛

  你会发现说的都是批量 insert 的时候, BigDecimal 有精度丢失

  单条插入的时候,是没有精度丢失的

  然后了,大家试出了一条件论: 批量插入数据时,如果插入的数据精度不统一,最终入库的数据精度统一按最低的精度入库 

  虽说我们只是查询,莫非也需要 精度统一 ?

  精度统一

  试试呗,反正又不要钱

  重启,神奇的事情发生了

  .3 它回来了! 相信此刻的你肯定有一种与知己久别重逢的激动

  问题貌似解决了,但说实话,这种处理方式你用的放心吗?

  升级 mssql-jdbc 版本

  我们好好捋一下,程序从 SQL Server 获取数据,经历了哪些环节?

  只有三个: MyBatis-Plus  ->  mssql-jdbc ->  SQL Server 

  前面我们已经排除了 SQL Server 和 MyBatis-Plus 

  那问题肯定就出在 mssql-jdbc 身上了

  问题又来了,该如何从 mssql-jdbc 上找问题了?

  开源的东西从它的官方找相关的 issue ,肯定不止我们遇到这样的问题,那么肯定有人会给官方提了 issue 

   issue 地址: https://github.com/microsoft/mssql-jdbc/issues 

  直接搜索 BigDecimal ,像这样

  回车之后,你会发现,原来你不是一个人在战斗

  那就去里面找呗,发现 #1489 跟我们的问题有点像,仔细去读,发现关联了 #1912

  读到 1912 的末尾,你会发现又关联了 #2051,我们去看看 2051

  那就是在这里修复了呀,那它关联的版本是哪个了?

  然后我们在回到我们搜索 BigDecimal 相关 issue 的时候,你会发现

   12.2.0 已经发布了

  如果觉得看英文的费劲,那就看中文的:Microsoft JDBC Driver for SQL Server 发行说明

  这总看得懂了吧

  那就将 mssql-jdbc 升级到 12.2.0 试试

  入参不用统一精度,结果也正确了!

  但是,又开始转折了,你以为 12.2.0 就高枕无忧了?

   BigDecimal 的问题都延续到 12.3.0 了

  此刻大家的心情是怎样的,请评论区留言

总结

  1、当 mssql-jdbc 遇上 BigDecimal ,两种处理方式

    1.1  BigDecimal 类型的入参全部统一成最高精度

    1.2 版本升级到 12.2.0 ,但还是有问题,需要考虑业务是否会触发 12.2.0 的 bug 

  2、  mssql-jdbc 的 BigDecimal 的问题从 2016 年就开始出现了,到了现在( 2023 )还存在问题,我真的想对官方说一句

SOAP UI:获取响应时出错;尝试连接到 Microsoft SQL Server 时为 null

【中文标题】SOAP UI:获取响应时出错;尝试连接到 Microsoft SQL Server 时为 null【英文标题】:SOAP UI: Getting Error getting response; null when trying to connect to Microsoft SQL Server 【发布时间】:2020-06-12 15:49:16 【问题描述】:

我正在尝试通过创建 JDBC 请求从 SOAP ui 连接到 Microsoft sql server 2018,但收到响应时出错;日志中为空。

注意:我已将 mssql-jdbc-7.2.2.jre11 添加到 SOAP UI 的 ext 文件夹中。

External Folder

SOAP-UI 5.5 JDBC Request

驱动程序:com.microsoft.sqlserver.jdbc.SQLServerDriver

连接字符串:jdbc:microsoft:sqlserver://localhost;数据库名称=亚马逊;integratedSecurity=true;

查询:从用户库中选择 *

【问题讨论】:

请用您的代码示例更新您的问题 感谢您的反馈,相应地更新了问题...您能帮我解决这个问题吗 请尝试在 Groovy 脚本测试步骤中连接和查询并共享输出 我在运行 groovy scrip 时遇到以下错误// java.sql.SQLException:找不到适合 jdbc:microsoft:sqlserver://localhost; 的驱动程序;数据库名称=亚马逊;集成安全=真;行错误: 现在您可以进行故障排除了。要么是错误的 jar 版本,要么没有正确加载,要么你的连接字符串不正确。此外,您正在使用可能与 SoapUI 一起使用或不使用的集成安全性 【参考方案1】:

我不在 SoapUI 中使用 JDBC 步骤,而是在 Groovy 脚本测试步骤中执行我的数据库步骤。

我在使用用于 MS-SQL 的 jTDS 驱动程序方面也取得了更大的成功。

具有域身份验证/集成安全性的示例连接字符串是:

def conn = Sql.newInstance("""jdbc:jtds:sqlserver://SERVERNAME:1433/dbname;
                 useLOBs=false;
                 instance=SERVERNAME;
                 autoCommit=true;
                 useNTLMv2=true;
                 domain=MYDOMAIN",
                 "MyUser", "MyPassword", "net.sourceforge.jtds.jdbc.Driver""")

然后:

myDB.eachRow("select * from userbase;")
    //do something with the data

我还建议您选择特定的列,而不是 * 从那里您可以将数据放入文件或填充属性值或其他任何内容。

【讨论】:

以上是关于当 SQL Server(mssql-jdbc) 遇上 BigDecimal → 精度丢失,真坑!的主要内容,如果未能解决你的问题,请参考以下文章

JDBC SSL连接SQL Server

无法从 Wildfly 连接到 SQL Server 数据库

JPQL SQL Server CURRENT_DATE 语法不正确

SOAP UI:获取响应时出错;尝试连接到 Microsoft SQL Server 时为 null

当 WHERE 存在时 sql server 更新

当数据框的列名与 sql server 表列名不同时使用 sqlalchemy to_sql