java.time.Instant -> java.sql.Timestamp 在 Windows 与 *nix 上的表现不同(时区相关)
java.time.Instant -> java.sql.Timestamp behaving differently on Windows vs *nix (timezone-related)
在代码的某处我有 Timestamp ts = Timestamp.from(instant);
,顾名思义,instant 是 java.time.Instant
。 (为什么?为了让 Hibernate 4.x 通过 UserType
持续存在,JDK8 时间类型在 Hibernate 5 之前尚不支持)。
事实上,让我把代码放上去,这样就清楚了。日志语句就是针对这个问题的。
public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
if (value != null){
Timestamp ts = Timestamp.from((Instant) value);
log.info("nullSafeSet from " + value + " (as long: " + ((Instant) value).toEpochMilli() + ") to " + ts + " (as long: " + ts.getTime() + ")");
StandardBasicTypes.TIMESTAMP.nullSafeSet(st, ts, index, session);
} else {
st.setNull(index, Types.TIMESTAMP);
}
}
这就是问题所在。在 Windows(7、64 位,尽管无关紧要)上,时间戳的字符串表示对应于 UTC 值。
From: 2015-05-12T19:00:08.191Z (as long: 1431457208191)
to: 2015-05-12 19:00:08.191 (as long: 1431457208191)
On *nix (Linux, OS X), 字符串表示对应本地(EST,准确来说是EDT)时间:
From: 2015-05-12T19:16:54.488Z (as long: 1431458214488)
to: 2015-05-12 15:16:54.488 (as long: 1431458214488)
绝对时间和看到的一样。问题是 JDBC 驱动程序发送到 Oracle 数据库到 TIMESTAMP 字段的是 字符串表示或等效的 (我查看了网络流量,参数在二进制格式,因此不容易弄清楚到底发送了什么)。 相同的代码插入从 Windows 执行时的 UTC 等效时间和从 Unix 执行时的本地时间。
我已经检查了 user.timezone
系统 属性,这两种情况都是 America/New York
。机器本身也是如此。相同的 JDK8 版本,相同的应用服务器,相同的代码。
我对这种行为以及如何解决它感到困惑。
好吧,最终我找到了原因,它和我想的不一样。感谢@JBNizet 给我的提示让我走上了正确的道路。
如评论中所述,在执行特定方法时,一个系统 TimeZone.getDefault()
在 Windows 和“America/New York
上返回“UTC
” " 在 Unix 机器上。尽管两个系统都以 user.timezone="America/New York"
开始,但已通过日志记录确认。
真正的原因是在 Windows 机器上,我还在同一台服务器上部署了一个不同的 Web 应用程序。该应用程序是用 Grails 编写的,正在调用 TimeZone.setDefault("UTC")
,因此在 JVM 级别覆盖 , 通过我的应用程序中的 属性 设置的时区。我用 TimeZone.setDefault()
.
中的断点确认了这一点
在代码的某处我有 Timestamp ts = Timestamp.from(instant);
,顾名思义,instant 是 java.time.Instant
。 (为什么?为了让 Hibernate 4.x 通过 UserType
持续存在,JDK8 时间类型在 Hibernate 5 之前尚不支持)。
事实上,让我把代码放上去,这样就清楚了。日志语句就是针对这个问题的。
public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
if (value != null){
Timestamp ts = Timestamp.from((Instant) value);
log.info("nullSafeSet from " + value + " (as long: " + ((Instant) value).toEpochMilli() + ") to " + ts + " (as long: " + ts.getTime() + ")");
StandardBasicTypes.TIMESTAMP.nullSafeSet(st, ts, index, session);
} else {
st.setNull(index, Types.TIMESTAMP);
}
}
这就是问题所在。在 Windows(7、64 位,尽管无关紧要)上,时间戳的字符串表示对应于 UTC 值。
From: 2015-05-12T19:00:08.191Z (as long: 1431457208191)
to: 2015-05-12 19:00:08.191 (as long: 1431457208191)
On *nix (Linux, OS X), 字符串表示对应本地(EST,准确来说是EDT)时间:
From: 2015-05-12T19:16:54.488Z (as long: 1431458214488)
to: 2015-05-12 15:16:54.488 (as long: 1431458214488)
绝对时间和看到的一样。问题是 JDBC 驱动程序发送到 Oracle 数据库到 TIMESTAMP 字段的是 字符串表示或等效的 (我查看了网络流量,参数在二进制格式,因此不容易弄清楚到底发送了什么)。 相同的代码插入从 Windows 执行时的 UTC 等效时间和从 Unix 执行时的本地时间。
我已经检查了 user.timezone
系统 属性,这两种情况都是 America/New York
。机器本身也是如此。相同的 JDK8 版本,相同的应用服务器,相同的代码。
我对这种行为以及如何解决它感到困惑。
好吧,最终我找到了原因,它和我想的不一样。感谢@JBNizet 给我的提示让我走上了正确的道路。
如评论中所述,在执行特定方法时,一个系统 TimeZone.getDefault()
在 Windows 和“America/New York
上返回“UTC
” " 在 Unix 机器上。尽管两个系统都以 user.timezone="America/New York"
开始,但已通过日志记录确认。
真正的原因是在 Windows 机器上,我还在同一台服务器上部署了一个不同的 Web 应用程序。该应用程序是用 Grails 编写的,正在调用 TimeZone.setDefault("UTC")
,因此在 JVM 级别覆盖 , 通过我的应用程序中的 属性 设置的时区。我用 TimeZone.setDefault()
.