You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用PostgreSQL JDBC驱动存储OffsetDateTime至带时区时间戳列时抛类转换异常

PostgreSQL JDBC 更新OffsetDateTime到TIMESTAMP WITH TIMEZONE时的ClassCastException问题

问题背景

我有一个包含timestamp with timezone列的PostgreSQL表。根据JDBC 4.2+规范,JDBC类型TIMESTAMP_WITH_TIMEZONE应与OffsetDateTime类双向映射(JDBC驱动自动处理转换,无需用户代码干预),而且rs.getObject("ts_col", OffsetDateTime.class)这类读取操作运行完全正常。

但执行以下操作时触发了ClassCastException:

测试用SQL

create table timestamp_test
(
    id     bigserial primary key,
    ts_col timestamp with time zone
);

insert into timestamp_test(ts_col)
values (now());

Java测试代码

private final static String selectQuery = "select * from timestamp_test";
@Test
public void jdbcOffsetDateTimeTest()
{
    try (Connection conn = dataSource.getConnection())
    {
        PreparedStatement ps = conn.prepareStatement(selectQuery,
                ResultSet.TYPE_SCROLL_SENSITIVE,
                ResultSet.CONCUR_UPDATABLE);
        ResultSet rs = ps.executeQuery();
        rs.next();
        rs.updateObject("ts_col", OffsetDateTime.now());
        rs.updateRow();
        rs.close();
        ps.close();
    } catch (SQLException e)
    {
        log.error("Exception: {}", e.getMessage(), e);
    }
}

报错信息

java.lang.ClassCastException: class java.time.OffsetDateTime cannot be cast to class java.sql.Timestamp (java.time.OffsetDateTime is in module java.base of loader 'bootstrap'; java.sql.Timestamp is in module java.sql of loader 'platform')

    at org.postgresql.jdbc.PgResultSet.setRowBufferColumn(PgResultSet.java:2128)
    at org.postgresql.jdbc.PgResultSet.updateRowBuffer(PgResultSet.java:2165)
    at org.postgresql.jdbc.PgResultSet.updateRow(PgResultSet.java:1643)
    at com.zaxxer.hikari.pool.ProxyResultSet.updateRow(ProxyResultSet.java:69)
    at com.zaxxer.hikari.pool.HikariProxyResultSet.updateRow(HikariProxyResultSet.java)
    at st.notexi.springtest.DbTest.jdbcOffsetDateTimeTest(DbTest.java:116)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)

疑问

这种情况是正常的吗?还是我操作有误?是否必须手动转换为java.sql.Timestamp才能更新?我使用的是最新版Pg JDBC Driver 42.7.4。


问题解答

这是PostgreSQL JDBC驱动在**可更新结果集(Updatable ResultSet)**场景下的已知兼容性问题,并非你的操作错误。

原因分析

JDBC 4.2规范确实明确了TIMESTAMP_WITH_TIMEZONE与OffsetDateTime的双向映射,但PostgreSQL驱动对可更新结果集的updateObject方法支持不完善——驱动内部处理更新逻辑时,依然默认期望接收java.sql.Timestamp类型,而非Java 8+引入的时间API类型(如OffsetDateTime),直接传入OffsetDateTime就会触发类型转换异常。

可行解决方案

你有三种可选的解决方式:

  1. 手动转换为java.sql.Timestamp
    按你思路转换时,推荐用更精准的方式避免精度丢失:

    OffsetDateTime odt = OffsetDateTime.now();
    // 保留纳秒精度的转换方式
    rs.updateObject("ts_col", Timestamp.from(odt.toInstant()));
    
  2. 使用updateTimestamp方法(更适配)
    既然驱动内部期望Timestamp,直接调用专门的更新方法更稳妥:

    OffsetDateTime odt = OffsetDateTime.now();
    rs.updateTimestamp("ts_col", Timestamp.from(odt.toInstant()));
    
  3. 改用常规UPDATE语句(推荐长期方案)
    如果业务场景允许,放弃可更新结果集,用常规的PreparedStatement执行更新,这种场景下驱动能正确处理OffsetDateTime:

    String updateSql = "UPDATE timestamp_test SET ts_col = ? WHERE id = ?";
    try (PreparedStatement ps = conn.prepareStatement(updateSql)) {
        // 这里直接传OffsetDateTime即可,驱动会正确转换
        ps.setObject(1, OffsetDateTime.now());
        ps.setLong(2, rs.getLong("id"));
        ps.executeUpdate();
    }
    

补充说明

PostgreSQL驱动对Java 8时间API的支持主要集中在查询参数设置和结果集读取场景,可更新结果集属于相对小众的使用场景,当前版本(42.7.4)还存在支持缺口,后续版本可能会修复该问题。


内容的提问来源于stack exchange,提问作者BUKTOP

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 12:58:18