我遇到的问题是MySQL存储的日期时间值与客户端传递的不同。服务器以UTC运行,客户端在不同的时区运行。不管怎样,MySQL似乎在客户端和服务器时区之间转换日期时间值,即使SQL类型DATE
,TIME
和TIMESTAMP
都没有时区。到目前为止,我测试的其他数据库都没有这种行为。
以下代码可用于重现该问题。当服务器以UTC运行时,代码仅在客户端也以UTC运行时才有效。
try (Connection connection = this.dataSource.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(
"SELECT ? = DATE '1988-12-25', ? = TIME '15:09:02', ? = TIMESTAMP '1980-01-01 23:03:20'")) {
preparedStatement.setDate(1, java.sql.Date.valueOf("1988-12-25"));
preparedStatement.setTime(2, java.sql.Time.valueOf("15:09:02"));
preparedStatement.setTimestamp(3, java.sql.Timestamp.valueOf("1980-01-01 23:03:20"));
try (ResultSet resultSet = preparedStatement.executeQuery()) {
while (resultSet.next()) {
System.out.println(resultSet.getBoolean(1));
System.out.println(resultSet.getBoolean(2));
System.out.println(resultSet.getBoolean(3));
}
}
}
我正在使用
我的JDBC网址只是jdbc:mysql://host:port/database
修改
我的理由为什么时区不应该在这里发挥作用,并且不应该发生时区转换是双重的。首先在SQL级TIMESTAMP
上是TIMESTAMP WITHOUT TIME ZONE
的别名,强烈暗示与TIMESTAMP WITH TIME ZONE
不同,它的值没有时区。换句话说,值不是时间上的瞬间,而是本地日期时间值。
其次,java.sql.Timestamp
在JVM时区只是作为java.util.Date
的子类的人工制品。 (我知道java.util.Date
没有时区)。 java.sql.Timestamp
的Javadoc非常清楚表明关系仅用于实现目的。
我觉得Java SE 8 / JDBC 4.2 java.sql.Timestamp
映射到java.time.LocalDateTime
而不是java.time.ZonedDateTime
或java.time.OffsetDateTime
这一事实证实了这两个断言。< / p>
编辑2
我不明白为什么TIMESTAMP
值受时区转换的影响。与TIMESTAMP WITH TIME ZOONE
不同,这些是“本地”值,并且没有关联的时区,因此不应对其应用时区转换。
答案 0 :(得分:3)
我同意评论MySQL JDBC下的时区处理可能令人困惑,但在这种情况下......
MySQL存储的日期时间值与客户端传递的日期时间值不同
......不太正确。它在服务器时区的上下文中解释和/或显示相同日期时间值的字符串表示。
首先,您需要了解java.sql.Timestamp#valueOf
在运行Java虚拟机的本地时区中创建Timestamp值。因此,对于我的机器是在山区时间&#34;在加拿大(标准时间为UTC-7):
System.out.printf("Local (client) timezone is %s%n", TimeZone.getDefault().getID());
java.sql.Timestamp tStamp = java.sql.Timestamp.valueOf("1980-01-01 23:03:20");
SimpleDateFormat sdfLocal = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
SimpleDateFormat sdfUTC = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
sdfUTC.setCalendar(Calendar.getInstance(TimeZone.getTimeZone("UTC")));
System.out.println("tStamp = java.sql.Timestamp.valueOf(\"1980-01-01 23:03:20\")");
System.out.printf(" ... which is %s%n", sdfLocal.format(tStamp));
System.out.printf(" ... which is %s%n", sdfUTC.format(tStamp));
打印
Local (client) timezone is America/Edmonton
tStamp = java.sql.Timestamp.valueOf("1980-01-01 23:03:20")
... which is 1980-01-01 23:03:20 MST
... which is 1980-01-02 06:03:20 UTC
现在,当我们将Timestamp值传递给PreparedStatement并将其发送到使用UTC时区的MySQL服务器时,服务器会解释并显示该MySQL TIMESTAMP的字符串文字表示形式为UTC:
String connUrl = "jdbc:mysql://localhost/mydb";
try (Connection conn = DriverManager.getConnection(connUrl, myUid, myPwd)) {
String sql = "SELECT CAST((TIMESTAMP ?) AS CHAR) AS foo";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setTimestamp(1, tStamp);
try (ResultSet rs = ps.executeQuery()) {
rs.next();
System.out.printf("String representation of TIMESTAMP value at server: %s%n",
rs.getString(1));
}
}
}
制造
String representation of TIMESTAMP value at server: 1980-01-02 06:03:20
如果MySQL服务器在多伦多时间(标准时间为UTC-5)下运行,则输出将是......
String representation of TIMESTAMP value at server: 1980-01-02 01:03:20
...不是因为MySQL TIMESTAMP值不同,而是因为MySQL服务器时区上下文中该值的字符串表示不同。
答案 1 :(得分:0)
我想这就像Gord Thompson和spencer7593所述,服务器的时区和客户端连接字符串的时区不同,必须在时区之间进行转换。
我在阅读您的编辑时也学到了一些东西,因为java.sql.Timestamp不包含时区。
正如spencer7593所述:&#34;服务器正在解释服务器时区&#34;中的文字。
我的两种方法是:
使用连接字符串中的时区连接到服务器(UTC)(例如&amp; serverTimezone = Europe / Zurich)。
或者将带有您的本地时区(Calendar.getInstance())的日历实例传递给PreparedStatement.setTimestamp。