Oracle TIMESTAMP(6) 存微秒,但JDBC默认截断为毫秒;需启用oracle.jdbc.javaTimeSupport=true、用setObject+TIMESTAMP类型、禁用useFetchSizeWithLongColumn、ORM层自定义处理器,并优先用TIMESTAMP WITH TIME ZONE+Instant保障端到端微秒精度。
Oracle TIMESTAMP(6) 存的是微秒,Java java.time.LocalDateTime 默认不带时区但也不存微秒精度
oracle 的 timestamp(6) 实际保存到微秒(10⁻⁶ 秒),而 java 8+ 的 localdatetime 内部用纳秒(10⁻⁹ 秒)存储,但 jdbc 驱动在默认配置下会「截断」微秒位——不是四舍五入,是直接丢弃最后三位(即把微秒转成毫秒再存)。结果就是:你插入 12:00:00.123456,查出来可能变成 12:00:00.123000。
实操建议:
- 确认 JDBC URL 加了
oracle.jdbc.javaTimeSupport=true(Oracle 12.2+ 驱动必需,否则java.time.*类型会被降级为java.sql.Timestamp) - 使用
PreparedStatement.setObject(idx, localDateTime, JDBCType.TIMESTAMP),而不是setTimestamp()—— 后者强制走旧路径,必然丢失微秒 - 如果必须用
java.sql.Timestamp,记得手动补零:ts.setNanos(nanos / 1000 * 1000)(避免被驱动二次截断)
JDBC 驱动版本和 oracle.jdbc.useFetchSizeWithLongColumn 会影响微秒读取稳定性
Oracle 19c 官方驱动(ojdbc8-19.22.0.0)在批量查询 TIMESTAMP(6) 字段时,若开启长列优化(useFetchSizeWithLongColumn=true,默认 false),会导致部分行的微秒位全为 0。这不是 bug,是驱动为兼容旧协议做的妥协。
实操建议:
- 显式关闭该参数:
jdbc:oracle:thin:@//host:1521/orcl?oracle.jdbc.useFetchSizeWithLongColumn=false - 升级到 ojdbc11(21.10+)可彻底规避,它默认禁用该优化且原生支持微秒级
ResultSet.getObject(idx, LocalDateTime.class) - 测试时别只查单行——用
OFFSET 100 ROWS FETCH NEXT 10 ROWS ONLY多翻几页,微秒丢失常出现在非首屏结果中
java.time.Instant 和 TIMESTAMP WITH TIME ZONE 配合用才真正端到端保微秒
如果你的 Oracle 字段是 TIMESTAMP WITH TIME ZONE,而 Java 用 Instant,那微秒能完整往返;但换成 TIMESTAMP(6) + LocalDateTime,就等于主动放弃时区上下文——驱动会在转换时按 JVM 默认时区做隐式偏移,再叠加精度截断,误差可能达毫秒级甚至更多。
立即学习“Java免费学习笔记(深入)”;
实操建议:
- 业务逻辑真不需要时区?那就统一用
TIMESTAMP(6)+LocalDateTime,但务必在所有 DAO 层加单元测试验证微秒写入/读回一致性 - 需要跨系统时间对齐(比如和 Kafka EventTime 对齐),优先改字段为
TIMESTAMP WITH TIME ZONE,Java 端用Instant+setObject(..., Instant.class) - 别信
new Timestamp(System.currentTimeMillis())能塞进微秒——它 nanos 是 0,驱动不会帮你补
MyBatis 和 Hibernate 默认不处理微秒,必须显式覆盖类型处理器
MyBatis 的 BaseTypeHandler 和 Hibernate 的 AttributeConverter 在未指定时,都会退化到 java.sql.Timestamp 流程,微秒照样丢。尤其 MyBatis-Plus 的自动填充功能,fill = FieldFill.INSERT 用 LocalDateTime.now() 插入,实际落库只有毫秒。
实操建议:
- MyBatis:自定义
LocalDateTimeTypeHandler,重写setNonNullParameter,调用ps.setObject(i, parameter, Types.TIME_WITH_TIMEZONE)(注意不是TIMESTAMP) - Hibernate:写
@Converter(autoApply = true),convertToDatabaseColumn返回OffsetDateTime.ofInstant(instant, ZoneId.systemDefault()) - Spring Boot 配置里加
spring.jpa.properties.hibernate.jdbc.time_zone=UTC,避免 ORM 自己做时区转换干扰微秒值
微秒精度不是“开了就行”的开关,它卡在 JDBC 驱动、ORM 层、Oracle 字段定义、JVM 时区四个地方。漏掉任意一环,数据就悄悄变矮了。

















