使用 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(与大多数数据库一样)没有数据类型来存储带有时区的完整日期和时间。因此,您需要在 InstantLocalDateTime 数据类型之间做出选择。

我的食谱如下。


如果您想记录特定事件发生时的物理瞬间(真正的“时间戳”,通常是一些创建/修改/删除事件),那么使用:

Java:Instant(Java 8 或 Jodatime)。 JDBC:java.sql.Timestamp PostgreSQL:TIMESTAMP WITH TIMEZONE (TIMESTAMPTZ)

(不要让 PostgreSQL 特殊的数据类型 WITH TIMEZONE/WITHOUT TIMEZONE 迷惑你:它们实际上都没有存储时区)

一些样板代码:以下假定psPreparedStatementrsResultSettzUTC 是对应于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)),你会使用:

Java: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“几何(多多边形)”数据类型

在 Java 中创建一个 bytea 数组以传递给 Postgresql 存储过程

Java连接PostgreSQL数据库

Java连接PostgreSQL数据库