mysql connector/j不将sql日期转换为jvm的时区

lmvvr0a8  于 2021-06-20  发布在  Mysql
关注(0)|答案(1)|浏览(229)

假设满足以下条件:
两个主机(运行jvm的主机和运行mysql的主机)的硬件时钟都是以utc运行的,并且这些时钟是同步的(例如。g、 :使用ntp)。
mysql和jvm的时区是不同的(在我的示例中,mysql运行在 Europe/Moscow ( +03:00 )并且jvm正在使用 GMT+14:00 ).
在这种情况下,当当前日期表示(在 yyyy-MM-dd 格式)将不同于java和数据库透视图(数据库日期将滞后)。
我使用的是mysql connector/j8.0,默认情况下它是时区感知的(而不是5.1.46),所以只需设置 serverTimezone 连接属性到 Europe/Moscow ,以防驱动程序无法解析 @@time_zone 和/或 @@system_time_zone .
现在,考虑以下场景:
客户机存储当前时间戳(作为 java.sql.Timestamp )到数据库。
然后,同一个客户机只读取上述时间戳的日期部分(作为 java.sql.Date )回到jvm。
读取的日期分数预计会转换回jvm的时区(这是我观察到的oracle、postgresql和mssqlserver)。e。以下测试应成功:

import static java.lang.String.format;
import static java.lang.System.currentTimeMillis;
import static org.assertj.core.api.Assertions.assertThat;

import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Properties;
import java.util.TimeZone;

import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

public final class TimeZoneTestPartial {
    private static final TimeZone DEFAULT_TIME_ZONE = TimeZone.getTimeZone("GMT+14:00");

    private static final String URL = "jdbc:mysql://localhost:3306/sandbox";

    private static final Properties CONNECTION_INFO = new Properties();

    static {
        CONNECTION_INFO.setProperty("user", "...");
        CONNECTION_INFO.setProperty("password", "...");
        CONNECTION_INFO.setProperty("useSSL", "false");
        CONNECTION_INFO.setProperty("serverTimezone", "Europe/Moscow");
    }

    @BeforeClass
    public static void setUpOnce() {
        TimeZone.setDefault(DEFAULT_TIME_ZONE);
    }

    @Test
    @SuppressWarnings("static-method")
    public void testDate() throws SQLException {
        try (final Connection conn = DriverManager.getConnection(URL, CONNECTION_INFO)) {
            try (final Statement stmt = conn.createStatement()) {
                final String tableName = "date_with_time_zone_test";
                try {
                    stmt.executeUpdate(format("drop table %s",
                            tableName));
                } catch (@SuppressWarnings("unused") final SQLException ignored) {
                    // ignore
                }

                stmt.executeUpdate(format("create table %s (value %s not null)",
                        tableName,
                        getTimestampType()));

                final long clientTimeMillis = currentTimeMillis();

                try (final PreparedStatement pstmt = conn.prepareStatement(format("insert into %s (value) values (?)",
                        tableName))) {
                    pstmt.setTimestamp(1, new Timestamp(clientTimeMillis));
                    pstmt.executeUpdate();
                }

                final String selectSql = format("select * from %s", tableName);
                try (final ResultSet rset = stmt.executeQuery(selectSql)) {
                    assertThat(rset.next()).isTrue();
                    final Date date = rset.getDate(1);
                    assertThat(date).isNotNull();
                    assertThat(rset.next()).isFalse();

                    final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
                    format.setTimeZone(DEFAULT_TIME_ZONE);
                    assertThat(format.format(date))
                            .as("date fraction from the database")
                            .isEqualTo(format.format(new java.util.Date(clientTimeMillis)));
                }

                stmt.executeUpdate(format("drop table %s",
                        tableName));
            }
        }
    }

    private static String getTimestampType() {
        return "datetime"; //"timestamp";
    }
}

实际上,mysql的测试失败了。e、 ,与其他主流数据库不同,mysql驱动程序可能返回昨天的日期:if i store an SQL TIMESTAMP 然后读回一封信 SQL DATE ,日期部分将具有数据库的时区,而不是jvm的时区。
我是不是漏了什么?
如何配置mysql connector/j8.0以使其与其他jdbc驱动程序保持一致?

js81xvg6

js81xvg61#

sql时间戳和日期类型不包含时区信息。
类似地,java.util.date、java.sql.date和java.sql.timestamp类型不包含时区信息。java.util.date和java.sql.timestamp包含自1970年1月1日00:00:00 utc以来的毫秒数。
他们的 toString 方法使用系统默认时区,但这不影响它们的值。
由于时区信息在日期或时间戳数据中没有意义,因此不应在比较中使用它。
不要使用字符串形式比较值。完全不要使用SimpleDataFormat。相反,通过使用一个不受时区影响的比较,来比较每个数据所代表的实际的、有意义的数据。
最简单的方法是将数据转换为不那么模棱两可的localdate和localdatetime类型:

LocalDateTime localClientTime = new Timestamp(clientTimeMillis).toLocalDateTime();
assertThat(date.toLocalDate())
        .as("date fraction from the database")
        .isEqualTo(localClientTime.toLocalDate());

相关问题