使用 Java 在 PostgreSQL 中存储时间的最推荐方法是啥?
Posted
技术标签:
【中文标题】使用 Java 在 PostgreSQL 中存储时间的最推荐方法是啥?【英文标题】:What is the most recommended way to store time in PostgreSQL using Java?使用 Java 在 PostgreSQL 中存储时间的最推荐方法是什么? 【发布时间】:2011-10-01 10:06:22 【问题描述】:我在 PostgreSQL 数据库中存储了两个日期。第一个是网页的访问数据,第二个日期是网页最后一次修改的日期(这个是get as long)。
我有些怀疑存储这些值的最佳策略是什么。
我只需要日/月/年和小时:秒,这仅用于统计建议。
所以,有些疑问:
最好存储时间长并在恢复信息时进行转换,还是以上述数据格式存储? 最好是在软件上设置访问日期还是在数据库中插入? 在 Java 中,处理日期的最佳类是什么?【问题讨论】:
“日期”这个词被重载了,但它最常见的意思是年-月-日,没有时间。也许你最好在帖子标题和正文中说“日期时间”或“时间戳”。 【参考方案1】:我在代码中使用 java.util.Date 将 Date 存储在查询的参数中。我正在使用 Spring JPA 并使用 @Query 注释。实施成功了。
【讨论】:
【参考方案2】:任何在 PostgreSQL 中存储日期和时间数据的策略,IMO,都应该依赖于这两点:
您的解决方案应该从不依赖于服务器或客户端的时区设置。 目前,PostgreSQL(与大多数数据库一样)没有数据类型来存储带有时区的完整日期和时间。因此,您需要在Instant
或 LocalDateTime
数据类型之间做出选择。
我的食谱如下。
如果您想记录特定事件发生时的物理瞬间(真正的“时间戳”,通常是一些创建/修改/删除事件),那么使用:
Java:Instant
(Java 8 或 Jodatime)。
JDBC:java.sql.Timestamp
PostgreSQL:TIMESTAMP WITH TIMEZONE
(TIMESTAMPTZ
)
(不要让 PostgreSQL 特殊的数据类型 WITH TIMEZONE
/WITHOUT TIMEZONE
迷惑你:它们实际上都没有存储时区)
一些样板代码:以下假定ps
是PreparedStatement
、rs
和ResultSet
和tzUTC
是对应于UTC
时区的静态Calendar
对象。
public static final Calendar tzUTC = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
将Instant
写入数据库TIMESTAMPTZ
:
Instant instant = ...;
Timestamp ts = instant != null ? Timestamp.from(instant) : null;
ps.setTimestamp(col, ts, tzUTC); // column is TIMESTAMPTZ!
从数据库TIMESTAMPTZ
中读取Instant
:
Timestamp ts = rs.getTimestamp(col,tzUTC); // column is TIMESTAMPTZ
Instant inst = ts !=null ? ts.toInstant() : null;
如果您的 PG 类型是 TIMESTAMPTZ
,这可以安全地工作(在这种情况下,calendarUTC
在该代码中无效;但始终建议不要依赖默认时区)。
“安全”意味着结果将不依赖于服务器或数据库时区,或时区信息:操作是完全可逆的,无论时区设置发生什么,您总是会得到相同的“即时时间”,你最初在 Java 端有。
如果您处理的不是时间戳(物理时间线上的瞬间),而是“公民”本地日期时间(即字段集year-month-day hour:min:sec(:msecs)
),你会使用:
LocalDateTime
(Java 8 或 Jodatime)。
JDBC:java.sql.Timestamp
PostgreSQL:TIMESTAMP WITHOUT TIMEZONE
(TIMESTAMP
)
从数据库TIMESTAMP
中读取LocalDateTime
:
Timestamp ts = rs.getTimestamp(col, tzUTC); //
LocalDateTime localDt = null;
if( ts != null )
localDt = LocalDateTime.ofInstant(Instant.ofEpochMilli(ts.getTime()), ZoneOffset.UTC);
将LocalDateTime
写入数据库TIMESTAMP
:
Timestamp ts = null;
if( localDt != null)
ts = new Timestamp(localDt.toInstant(ZoneOffset.UTC).toEpochMilli()), tzUTC);
ps.setTimestamp(colNum,ts, tzUTC);
同样,此策略是安全的,您可以安然入睡:如果您存储了 2011-10-30 23:59:30
,您将始终检索那些精确的字段(小时=23、分钟=59...等),无论如何 - 即使明天您的 Postgresql 服务器(或客户端)的时区会发生变化,或者您的 JVM 或您的操作系统时区会发生变化,或者您的国家/地区是否会修改其 DST 规则等。
添加:如果您想要(这似乎是一个自然的要求)存储完整的日期时间规范(ZonedDatetime
:时间戳和时区,其中隐含地还包括完整的民用日期时间信息 - 加上时区).. . 那么我有个坏消息要告诉你:PostgreSQL 没有这种数据类型(据我所知,其他数据库也没有)。您必须设计自己的存储,也许在一对字段中:可能是上述两种类型(高度冗余,但对于检索和计算很有效),或者其中一种加上时间偏移(您丢失时区信息,一些计算变成困难,有些不可能),或者其中之一加上时区(作为字符串;一些计算可能非常昂贵)。
【讨论】:
很好的答案,但在 Java 8 及更高版本中已经过时,我们现在有内置的 java.time 框架(受 Joda-Time 启发)。 @BasilBourque,我从未使用过 JodaTime,而且我刚刚开始使用 Java 8。对我来说,这个答案几乎可以与 Java 8 API 一对一地翻译。 无法确定 yoda,但在使用 Java 8 Instant 时不应使用 TZ 保存时间戳。即时是UTC。时间戳也是如此。它不需要 TZ,保存它可能会导致错误。使用 Jooq 花了我几个小时,因为它正确地写入了 TIMESTAMPTZ,但在读取值时没有按预期使用时区。 关于时区文字的存储,我由 Craig Ringer 回答的问题可能也有帮助:***.com/questions/12331962/…。存储原始客户端在即时时间选择的时区是一个好主意,特别是对于日历应用程序。 @mlorber 我意识到您的评论已经很老了,但它不正确,您绝对应该在几乎所有情况下都使用带有 tz 的时间戳。我推荐阅读***.com/questions/6627289/…。抓住:您确实需要将您的 JVM 设置为 UTC,以便 postgres 正确解释传入的字符串。【参考方案3】:java.time
这不是很漂亮,但这对我来说是这样的
ZonedDateTime receivedTimestamp = some_ts_value;
Timestamp ts = new Timestamp(receivedTimestamp.toInstant().toEpochMilli());
ps.setTimestamp(
1,
ts,
Calendar.getInstance(TimeZone.getTimeZone(receivedTimestamp.getZone()))
);
【讨论】:
(a) 这个答案并不完全符合问题的意图。 (b) 此代码有更直接的方法。诸如java.sql.Timestamp
之类的旧类在 Java 8 中进行了改进,后来又使用了方便的方法来与新的 java.time 类型相互转换。所以:java.sql.Timestamp ts = java.sql.Timestamp.fromInstant( myZonedDateTime.toInstant() );
以上是关于使用 Java 在 PostgreSQL 中存储时间的最推荐方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章
Postgresql 11:存储过程调用错误 - 要调用过程,请使用 CALL、Java
为啥我在从 Java 批量执行 PostgreSQL 存储过程时收到错误消息,提示“未预期结果”?
在java中存储PostgreSQL/PostGIS“几何(多多边形)”数据类型