oracle number列映射为long必然精度丢失,因number是十进制任意精度类型,而long仅支持64位整数;应统一用getbigdecimal()再按需安全转换。
oracle 的 number 列直接映射为 long 会丢精度,不是 jdbc 驱动没做好,而是语义根本错配。 oracle 的 number 是十进制任意精度类型(比如 number(18,2) 或无精度声明的 number),而 java 的 long 是 64 位有符号整数,最大只到 9223372036854775807。一旦数据库里存了 9999999999999999999(19 位)或带小数的 number,用 getlong() 强转就是截断或溢出——这不是“偶尔不准”,是必然失败。
为什么 getLong() 会静默出错
Oracle JDBC 驱动对 NUMBER 的默认映射其实是 BigDecimal,但只要你显式调用 rs.getLong("col"),驱动就会尝试把内部的 BigDecimal 转成 long。这个过程不报错,只做截断或饱和处理(如超范围返回 Long.MAX_VALUE 或 Long.MIN_VALUE),日志里完全看不到异常。
- 如果数据库字段是
NUMBER(20),存了10000000000000000000(19 个零),getLong()返回-2^63或其他不可预期值 - 如果字段实际是
NUMBER(10,2)(比如123.45),getLong()直接丢掉小数部分,变成123,且不警告 - MyBatis 默认用
Long接NUMBER字段时,也会触发同样的隐式转换,尤其在resultType="map"或未配typeHandler场景下
正确做法:一律用 getBigDecimal() 再按需转
拿到 BigDecimal 后,是否转 long、怎么转,由你全权控制,而不是交给 JDBC 猜。
- 确认字段纯整数且业务允许截断:用
bigDecimal.longValueExact()—— 超出long范围直接抛ArithmeticException,暴露问题 - 需要保留小数参与后续计算:全程用
BigDecimal,加减乘除都调用对应方法,避免doubleValue()中途介入 - 仅用于展示或前端传参:先
setScale(0, RoundingMode.HALF_UP)再longValueExact(),明确表达“这是四舍五入后的整数” - MyBatis 中,显式配置
<result property="amount" column="amount" javatype="java.math.BigDecimal"></result>,堵死自动转Long的路径
to_number() 和 cast() 在 SQL 层不能解决根本问题
有人在 SQL 里写 CAST(amount AS NUMBER(10)) 或 TO_NUMBER(amount, '9999999999'),以为能“提前限定范围”。这反而危险:
-
CAST只影响 Oracle 内部运算精度,不改变 JDBC 返回类型策略;驱动仍可能把结果当NUMBER处理,再被你的getLong()拦截 -
TO_NUMBER带格式串本质是字符串转数字,若原始NUMBER已超格式宽度(如填了'999'却查出1000),直接报 ORA-01722 - 真正该在 SQL 层做的,是用
TRUNC(amount)或ROUND(amount)显式取整,再配合getBigDecimal()拿结果,而非依赖类型转换
最易被忽略的一点:精度丢失从来不在 ResultSet 这一层结束。就算你用了 getBigDecimal(),如果后续又调了 toString() 去拼 SQL、或用 String.format("%.2f", bd) 做格式化,就可能触发 double 中间态——BigDecimal.toString() 才是安全的输出方式。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











