java - MySQL 客户端与服务器时区

标签 java mysql datetime jdbc timezone

我遇到了一个问题,即 MySQL 存储的日期时间值与客户端传递的日期时间值不同。服务器在 UTC 中运行,客户端在不同的时区中运行。 MySQL 似乎以某种方式在客户端和服务器时区之间转换日期时间值,即使 SQL 类型 DATETIMETIMESTAMP 都没有时间区。到目前为止,我测试过的其他数据库都没有这种行为。

以下代码可用于重现该问题。当服务器以 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));
    }
  }

}

我正在使用

  • MySQL 5.7.14
  • mysql-connector-java 6.0.5
  • 甲骨文 Java 1.8.0_131

我的 JDBC URL 只是 jdbc:mysql://host:port/database

编辑

我为什么时区不应在这里发挥作用并且不应发生时区转换的原因有两个。首先在 SQL 级别 TIMESTAMPTIMESTAMP 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.ZonedDateTimejava.time.OffsetDateTime

编辑2

我不明白为什么 TIMESTAMP 值要进行时区转换。与 TIMESTAMP WITH TIME ZOONE 不同,这些是“本地”值,没有关联的时区,因此不应对其应用时区转换。

最佳答案

我同意 MySQL JDBC 下的时区处理可能令人困惑的评论,但在这种情况下......

MySQL stores different date time values than the client passes

...不太正确。它在服务器时区的上下文中解释和/或显示相同日期时间值的字符串表示

首先您需要了解 java.sql.Timestamp#valueOf 在 Java 虚拟机运行的本地时区中创建时间戳值。因此,对于我在加拿大使用“山区时间”(标准时间为 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

现在,当我们将该时间戳值传递给 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 服务器时区上下文中的字符串表示不同。 p>

关于java - MySQL 客户端与服务器时区,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43560374/

相关文章:

java - eclipse junit 差异窗口

datetime - 如何进行日期/时间比较

ruby-on-rails - Rails -- 无法在表单中设置 date_select 的值

MYSQL导入: Cannot get geometry object from data you send to the GEOMETRY field

java - 从日历中获取日期之前的 18 年

java - 使用 java 和 activemq-all 库进行 STOMP 心跳跟踪

java - 使用循环从字符串中删除特定字符串

java - 为什么我的程序不能正确处理字符编码?

sql - Mysql 使用 JOIN 和 WHERE 语句。这是什么区别?

PHP:准备 PDO 数据库语句的更简洁方法?