Oracle 19c 默认时区为 UTC,Spring Boot 3 使用 JVM 本地时区解析时间,导致 Asia/Shanghai 场景下读取时间少8小时;需通过 connection-init-sql 设置会话时区并避免依赖 Jackson 或 JVM 参数。
Oracle 19c 默认时区是 UTC,Spring Boot 3 默认用 JVM 本地时区解析时间
oracle 19c 数据库实例默认 time_zone 是 utc(即 +00:00),而 spring boot 3 运行在 jdk 17+ 上,java.time 类型(如 timestamp、localdatetime)从数据库读取时,jdbc 驱动会按数据库会话时区做隐式转换。如果没显式设置会话时区,驱动就按数据库全局时区(utc)解释值,但你的业务逻辑和前端展示预期是 asia/shanghai(utc+8),结果就少 8 小时。
这不是 bug,是 JDBC 规范行为:Oracle 驱动不自动“猜”你想要哪个时区,它忠实地把数据库存的 UTC 时间戳按 UTC 解析成 Instant,再由 Spring 或 Jackson 转成本地 LocalDateTime —— 这一步若没指定时区,就掉进 JVM 默认时区陷阱里。
ojdbc8 不支持 serverTimezone 参数,别往 URL 里加
MySQL 的 serverTimezone=Asia/Shanghai 在 Oracle JDBC URL 中完全无效,加了也白加,还会让配置看起来“已解决”,实则掩盖问题。Oracle 驱动根本不认这个参数,ojdbc8 对应的合法时区控制方式只有两个:
- 在连接建立后,执行
ALTER SESSION SET TIME_ZONE = 'Asia/Shanghai' - 或更稳妥地,在
application.yml中通过spring.datasource.hikari.connection-init-sql自动初始化会话时区
示例配置:
spring:
datasource:
hikari:
connection-init-sql: ALTER SESSION SET TIME_ZONE = 'Asia/Shanghai'
注意:必须用单引号包裹 Asia/Shanghai,双引号或不带引号会报 ORA-00922。
Jackson 序列化与数据库读写是两套独立时区逻辑
很多人只配了 spring.jackson.time-zone: Asia/Shanghai,发现接口返回时间对了,但数据库查出来还是 UTC 时间——因为这是两个环节:
-
spring.jackson.time-zone只影响 HTTP 响应体中 JSON 的序列化(比如@ResponseBody返回的LocalDateTime) - 数据库读写时区由 JDBC 驱动 + Oracle 会话时区决定,和 Jackson 无关
- 如果你用的是
java.util.Date或Timestamp,还可能被 JPA/Hibernate 再裹一层时区转换
所以必须同时做两件事:
① 用 connection-init-sql 统一会话时区;
② 若返回 JSON 时间字段仍不准,检查是否用了 @JsonFormat 覆盖了全局配置,或 DTO 字段类型是否为 Instant(它本就不该带时区偏移)。
别依赖 JVM -Duser.timezone 启动参数来“一劳永逸”
在容器或 K8s 环境里设 -Duser.timezone=Asia/Shanghai 看似简单,但它会影响所有时间相关 API,包括日志时间、定时任务触发点、甚至 HikariCP 的空闲连接清理逻辑。更麻烦的是,Oracle 驱动内部某些路径(如 Wallet 初始化)会绕过该参数,导致部分连接仍用 UTC 解析时间。
真正可控的方式是:
- 数据库层:确保
v$parameter.TIME_ZONE是+08:00(非必须,但统一更好) - 连接层:用
connection-init-sql强制每个连接使用Asia/Shanghai - 应用层:所有时间字段优先用
ZonedDateTime或带时区的OffsetDateTime,避免LocalDateTime模糊语义
最易忽略的一点:Oracle 的 TIMESTAMP WITH TIME ZONE 类型能存时区信息,但如果你用的是普通 TIMESTAMP,那它本质上就是“无时区的时间点”,驱动只能靠会话时区去解释——这意味着,会话时区错了,读出来的值就永远差 8 小时,且无法在应用层补救。


















