Oracle NUMBER映射Java应统一用getBigDecimal(),禁用getDouble()等强转;需按业务语义主动调用setScale()规整小数位,避免依赖数据库定义;JSON解析金额须直转BigDecimal,防止double中间态失真。
Oracle NUMBER类型映射到Java时为什么总丢精度
因为jdbc默认把number当作java.math.bigdecimal返回,但很多开发者用getdouble()或getint()强转,一转就四舍五入或截断。比如oracle里存999999999999999.999,用getdouble()取出来可能变成1000000000000000.0——这不是数据库问题,是java浮点数表达能力不够。
正确做法始终用getBigDecimal(),哪怕字段定义是NUMBER(10,0)也别图省事换其他类型。
- 所有
NUMBER列(无论有没有小数位)都应统一用ResultSet.getBigDecimal(String)获取 - 如果业务层真要转
long或int,先确认值范围且无小数部分,再调用bigDecimal.longValueExact(),它会在溢出或有小数时抛ArithmeticException,比静默截断更安全 - 避免用
new BigDecimal(double)构造,比如new BigDecimal(rs.getDouble("col"))——double本身已失真,再包一层毫无意义
BigDecimal的scale怎么控制才不崩
Oracle的NUMBER(p,s)中s是小数位数,但JDBC返回的BigDecimal的scale不一定等于s。比如NUMBER(5,2)存123.00,getBigDecimal()可能返回123(scale=0)而不是123.00(scale=2)。这在金额校验、前端展示、等值比较时容易出错。
关键不是“保持原scale”,而是“按业务语义确定scale”。比如金额字段必须保留两位小数,那就得主动规整:
- 用
bigDecimal.setScale(2, RoundingMode.HALF_UP)强制设为两位,不要依赖数据库定义 - 如果字段实际存储的是整数ID(如
NUMBER(19,0)),但ORM框架(如MyBatis)自动做了setScale(0),反而导致compareTo()异常——此时应检查是否配置了jdbcType=NUMERIC而非jdbcType=DECIMAL,二者对scale处理逻辑不同 - Spring JDBC的
BeanPropertyRowMapper默认不会调整scale,得靠自定义RowMapper或实体类的setter里做setScale
MyBatis里NUMBER字段为啥有时变null有时变0
常见于使用<resultmap></resultmap>时写了jdbcType=NUMBER但没配javaType,MyBatis会尝试用默认类型匹配,遇到空值或特殊值(如Oracle的NULL、0、负零)就行为不一致。更隐蔽的是NUMBER列允许为空,但Java字段是BigDecimal(非BigDecimal包装类),MyBatis可能填0而不是null。
- 显式声明
javaType="java.math.BigDecimal",并确保字段可为null(即用包装类型,不用BigDecimal的子类或原始类型) - 在
<resultmap></resultmap>中为该字段加nullValue="null"属性,防止MyBatis把空字符串或全空格转成0 - 如果用注解方式(
@Select),记得在@Results里指定column="xxx", javaType=BigDecimal.class,别只写column
批量插入时BigDecimal精度丢失的隐藏路径
用JdbcTemplate.batchUpdate()或MyBatis <foreach></foreach>批量插NUMBER字段时,如果传入的BigDecimal对象来自Double.toString()或JSON解析(如Jackson默认把数字转成Double再转BigDecimal),中间经过double就已失真。例如JSON里"price": 19.99被Jackson读成Double,再转BigDecimal可能得到19.990000000000002。
- JSON反序列化时,对金额字段用
@JsonDeserialize(using = BigDecimalDeserializer.class),避免走double中间态 - JDBC批处理前,用
bigDecimal.round(new MathContext(precision, RoundingMode.HALF_UP))按Oracle定义的p位精度预处理,否则超长数字可能被数据库截断而不报错 - Oracle驱动参数
sendStringParametersAsUnicode=false可减少字符编码干扰,但对精度无影响;真正有用的是确保连接URL含oracle.jdbc.J2EE13Compliant=true(较新驱动默认开启),它会让setBigDecimal更严格遵循JDBC规范
最麻烦的不是不知道用BigDecimal,而是用了却没管它的scale和precision——这两个值一旦错,后续所有计算、比较、序列化都会连锁出错,而且问题常在压测或特定数据组合下才暴露。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











