Java LocalTime与OffsetTime存入PostgreSQL后值异常的原因排查
环境配置
- JVM运行时区:
Europe/Dublin(通过TimeZone.getDefault().getID()确认) - Spring/Hibernate JDBC时区配置:
spring.jpa.properties.hibernate.jdbc.time_zone=UTC
- PostgreSQL数据库时区:UTC(配置文件设置)
postgres=# select name, setting, source from pg_settings where name='TimeZone'; name | setting | source ----------+---------+-------------------- TimeZone | Etc/UTC | configuration file
Java实体类定义
@Column(name = "local_time") private LocalTime localTime; @Column(name = "local_date_time") private LocalDateTime localDateTime; @Column(name = "offset_time") private OffsetTime offsetTime;
调试与查询结果
JVM调试得到的Java对象值
LocalTime(对应SQLtime类型):01:32:13.283256(由LocalTime.ofInstant(now, Clock.systemDefaultZone().getZone())生成)LocalDateTime(对应SQLtimestamp类型):2022-12-06T01:32:13.283256(由LocalDateTime.ofInstant(now, Clock.systemDefaultZone().getZone())生成)OffsetTime(对应SQLtime with time zone类型):01:32:13.283256Z(由OffsetTime.ofInstant(now, Clock.systemDefaultZone().getZone())生成)
数据库直接查询结果
select * from item; id | local_time | local_date_time | offset_time ----+------------+----------------------------+------------- 1 | 00:32:13 | 2022-12-06 01:32:13.283256 | 00:32:13+00
问题
为何local_time和offset_time列的值比JVM中的值早1小时?它们不应与local_date_time一致为01:32而非00:32吗?为何直接查询数据库时只有local_date_time的值和JVM中一致?
原因分析
1. LocalDateTime的处理逻辑
LocalDateTime本身不带时区信息,Hibernate在配置hibernate.jdbc.time_zone=UTC时,不会对LocalDateTime做时区转换——它会直接将JVM中生成的字面量时间写入数据库的timestamp字段,所以数据库查询结果和JVM调试值完全一致。
2. LocalTime的处理逻辑
虽然LocalTime也不带时区,但Hibernate会将JVM本地时区(Europe/Dublin)的LocalTime视为该时区的实际时间,再按照hibernate.jdbc.time_zone=UTC的配置转换为UTC时区的时间后存入time字段。
你遇到的时差问题,是因为测试时间点Europe/Dublin处于夏令时(UTC+1时区):JVM中的01:32是Dublin本地时间,转换为UTC时间就是00:32,所以数据库中存储的是转换后的UTC时间。
3. OffsetTime的处理逻辑
PostgreSQL的time with time zone类型本质上存储的是UTC时间,查询时会根据会话时区展示(你的数据库会话时区是UTC,所以直接显示UTC时间)。
Hibernate处理OffsetTime时,同样会应用hibernate.jdbc.time_zone配置:即使你的OffsetTime显示为01:32Z(UTC时间),Hibernate仍会将其视为JVM本地时区的时间进行转换——如果JVM时区是UTC+1,转换后的UTC时间就是00:32,最终存入数据库的就是这个值。
内容的提问来源于stack exchange,提问作者bobbyrne01

