MyBatis无法直接用ZonedDateTime.class映射Oracle TIMESTAMP WITH TIME ZONE字段,因ojdbc驱动未实现该转换;必须通过oracle.sql.TIMESTAMPTZ显式获取后调toInstant()转为Instant,且需配置oracle.jdbc.javaTimeSupport=true连接参数。

MyBatis 里不能直接用 ZonedDateTime.class 拿 TIMESTAMP WITH TIME ZONE
Oracle 的 TIMESTAMP WITH TIME ZONE 字段,JDBC 驱动(ojdbc)默认映射为私有类型 oracle.sql.TIMESTAMPTZ,不是标准 JDBC 类型。MyBatis 的 ResultSet.getObject("col", ZonedDateTime.class) 会直接抛 SQLFeatureNotSupportedException——这不是配置问题,是驱动没实现该转换。
常见错误现象:
• 在 resultMap 中写 javaType="java.time.ZonedDateTime" + jdbcType="TIMESTAMP",查出来是 null 或报错
• 强转 rs.getObject("ts_tz") 成 TIMESTAMPTZ 后调 toJdbc(),结果时区信息丢失,变成本地时区的 Timestamp
- 必须用
rs.getObject("col", oracle.sql.TIMESTAMPTZ.class)显式获取原生对象 - 再调
timestamptz.toInstant()(ojdbc 12.2+ 支持)转成Instant,这是唯一保微秒+时区的端到端路径 - 若用 MyBatis 自定义
TypeHandler,handler 内部也得走这条路径,不能依赖ResultSet.getTimestamp()
jdbcType="TIMESTAMP" 不等于 Oracle 的 TIMESTAMP WITH TIME ZONE
MyBatis 的 jdbcType 是 JDBC 标准枚举值,TIMESTAMP 对应的是无时区的 java.sql.Timestamp,和 Oracle 的 TIMESTAMP WITH TIME ZONE 是两类东西。设错会导致驱动静默降级:字段明明是带时区的,但 MyBatis 按普通 TIMESTAMP 处理,结果时间被按 JVM 默认时区解释,偏移 8 小时很常见。
- Oracle 建表用
TIMESTAMP WITH TIME ZONE→ MyBatis 映射必须避开jdbcType="TIMESTAMP" - 插入时,用
ps.setObject(1, instant, JDBCType.TIMESTAMP_WITH_TIME_ZONE)(需 ojdbc 12.2+) - 查询时,
resultMap中不要写jdbcType,让驱动自己选;或显式配typeHandler="com.example.TimestamptzTypeHandler" - 别信 MyBatis 文档里 “支持所有 JDBCType” 的说法——
TIMESTAMP_WITH_TIME_ZONE是 Oracle 私有扩展,标准 JDBC 没这常量
为什么 rs.getTimestamp("col", Calendar.getInstance(TimeZone.getTimeZone("UTC"))) 还是错的
这个调用只对普通 TIMESTAMP 有效,对 TIMESTAMP WITH TIME ZONE 没用。因为 TIMESTAMPTZ 本身已含时区,驱动不会拿你传的 Calendar 去“解释”,而是直接忽略它。强行传反而可能触发驱动内部不一致逻辑,比如把 UTC Calendar 和字段自带时区叠加计算,导致双重偏移。
-
getTimestamp(String, Calendar)的Calendar参数只影响无时区TIMESTAMP的解析语义,跟TIMESTAMPTZ无关 - 真正要的是字段原始时区值,不是“按某个时区解释后的本地时间”
- 验证方式:用 SQL*Plus 查
SELECT TO_CHAR(ts_tz, 'YYYY-MM-DD HH24:MI:SS TZR') FROM t,对比 Java 侧拿到的Instant.toString(),二者毫秒数必须完全一致
Spring Boot + MyBatis 中最简可行方案
不改表结构、不升级驱动的前提下,能稳定读写的最小改动是绕过 MyBatis 默认映射,用原生 TIMESTAMPTZ + Instant 手动桥接。复杂点在于:这个桥接不能只做一次,得在所有 DAO 层统一收口,否则各处逻辑不一致。
- 写一个
TimestamptzTypeHandler,继承BaseTypeHandler<Instant>,setNonNullParameter调ps.setObject(idx, instant, Types.OTHER),getNullableResult先rs.getObject(idx, TIMESTAMPTZ.class)再.toInstant() - 在
application.yml里配mybatis.configuration.type-handlers-package: com.example.handler - 实体类字段用
Instant,resultMap中删掉jdbcType,只留javaType="java.time.Instant"和typeHandler - 注意:ojdbc 12.1 及更早版本不支持
TIMESTAMPTZ.toInstant(),得用timestamptz.timestampValue(conn).toInstant()兼容
真正容易被忽略的是连接参数:oracle.jdbc.javaTimeSupport=true 必须加在 JDBC URL 里,否则 getObject(..., TIMESTAMPTZ.class) 会退化成 Timestamp,时区信息彻底丢光。


















