使用PostgreSQL JDBC驱动存储OffsetDateTime至带时区时间戳列时抛类转换异常
问题背景
我有一个包含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就会触发类型转换异常。
可行解决方案
你有三种可选的解决方式:
手动转换为
java.sql.Timestamp
按你思路转换时,推荐用更精准的方式避免精度丢失:OffsetDateTime odt = OffsetDateTime.now(); // 保留纳秒精度的转换方式 rs.updateObject("ts_col", Timestamp.from(odt.toInstant()));使用
updateTimestamp方法(更适配)
既然驱动内部期望Timestamp,直接调用专门的更新方法更稳妥:OffsetDateTime odt = OffsetDateTime.now(); rs.updateTimestamp("ts_col", Timestamp.from(odt.toInstant()));改用常规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

