Oracle JDBC驱动将NUMBER(38)无小数位列默认映射为Long,但Long最大值约9×10¹⁸远小于NUMBER(38)的10³⁸,导致数值溢出截断或抛Numeric Overflow异常。
Java读取Oracle NUMBER(38)时自动变成Long导致溢出
oracle的number(38)能存最大约10³⁸的整数,远超java long的2⁶³−1(约9×10¹⁸)。jdbc默认把number无小数位的值映射为long,一旦数据库值超过long.max_value,就会静默截断或抛sqlexception: numeric overflow。
这不是驱动bug,而是Oracle JDBC驱动的默认行为——它根据列的精度和小数位数做类型推测:当scale == 0且precision 时用<code>Long;否则 fallback 到BigDecimal。但NUMBER(38)的precision=38,按理该走BigDecimal,可某些旧版驱动(如ojdbc6)或未显式设setForceBigDecimals(true)时仍可能错判。
- 确认驱动版本:ojdbc8+ 默认更保守,但仍建议显式控制
- 在
Connection创建后、执行查询前调用connection.unwrap(OracleConnection.class).setForceBigDecimals(true) - 或全局配置JDBC URL加
?oracle.jdbc.javaNumber=true(ojdbc8起支持) - 避免依赖
ResultSet.getLong()——直接用getBigDecimal()最稳
MyBatis里NUMBER(38)字段映射成BigInteger还是BigDecimal?
选BigDecimal。虽然NUMBER(38)常被当作大整数用,但Oracle NUMBER本质是十进制浮点类型,不保证一定是整数——它可能存123.45,也可能存123.0(scale=0)。用BigInteger会丢失小数部分,且MyBatis默认jdbcType="NUMERIC"绑定的就是java.math.BigDecimal。
- 实体类字段声明用
private BigDecimal amount;,别用BigInteger - XML中明确写
<result column="AMOUNT" property="amount" jdbctype="NUMERIC"></result>,不省略jdbcType - 若必须转
BigInteger,等业务逻辑层再调bigDecimal.toBigIntegerExact()——它会在有小数时抛ArithmeticException,帮你暴露数据问题 - 注意:
BigDecimal构造函数别用double参数(如new BigDecimal(123.45)),要用字符串,否则二进制浮点误差会污染精度
Spring Data JPA对NUMBER(38)的@Column precision/scale设置没用?
没用。JPA的@Column(precision = 38, scale = 0)只影响DDL生成(建表语句),不影响运行时JDBC读取行为。Hibernate读取时仍由JDBC驱动决定返回什么Java类型,和注解无关。
- 真正起作用的是JDBC层配置,比如
setForceBigDecimals(true)或URL参数 -
@Column的precision/scale对Oracle无效——Oracle不靠这个约束类型,它只看实际存的值 - 如果想让Hibernate强制用
BigDecimal,可在属性上加@Convert(converter = BigDecimalConverter.class),但纯属绕路,不如修JDBC配置 - 验证方式:打断点看
ResultSet.getObject("col")返回的getClass(),别信IDE的自动类型推导
从ResultSet取NUMBER(38)时getBigDecimal()返回null?
常见于字段允许NULL且数据库值确实是NULL,但更隐蔽的问题是:你调了getLong()或getInt()之后再调getBigDecimal(),JDBC规范规定同一列多次取值行为未定义,部分驱动会返回null或抛异常。
- 永远只调用一次getter:要么
getBigDecimal(),要么getObject()再强制转型 - 检查是否用了
getXXX()链式调用,比如rs.getLong("id") + rs.getBigDecimal("amount").doubleValue()——这里getLong()已消费该列 - NULL安全写法:
BigDecimal val = rs.getBigDecimal("col"); if (val != null) { ... } - 别用
getString()再解析——遇到科学计数法(如"1.23E+30")会丢精度
最麻烦的不是怎么读,而是团队里有人偷偷在DAO里写了rs.getLong(),上线后某天数据超过10¹⁹就崩。这种问题查日志看不到异常,只有数值不对,得翻代码逐行盯getter调用顺序。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










