Предположим, что выполнены следующие условия:
- На обоих хостах (на одном из которых JVM , а также на MySQL ) аппаратные часы работают в формате UTC, и эти часы синхронизируются (например, с использованием NTP).
- Часовые пояса MySQL и JVM отличаются (в моем примере MySQL работает в
Europe/Moscow
( +03:00
) часовой пояс и JVM использует GMT+14:00
).
В этом случае будут периоды в течение каждого дня, когда текущее представление даты (в формате yyyy-MM-dd
) будет отличаться от перспектив Java и базы данных (дата базы данных будет отставать).
Я использую MySQL Connector / J 8.0, который является часовым поясом по умолчанию (в отличие от 5.1.46), поэтому достаточно просто установить для свойства соединения serverTimezone
значение Europe/Moscow
, если драйвер не может проанализировать @@time_zone
и / или @@system_time_zone
.
Теперь рассмотрим следующий сценарий:
- Клиент сохраняет текущую метку времени (как экземпляр
java.sql.Timestamp
) в базе данных.
- Затем тот же клиент считывает только дробь даты вышеуказанной отметки времени (как
java.sql.Date
) обратно в JVM.
Ожидается, что считанная часть даты будет преобразована обратно во временную зону JVM (это то, что я наблюдаю для Oracle , PostgreSQL и MS SQL Server ), т.е. е. следующий тест должен пройти успешно:
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 - i. то есть, в отличие от других основных баз данных, драйвер MySQL может возвращать вчерашнюю дату: если я сохраню SQL TIMESTAMP
и прочту SQL DATE
, фракция даты будет иметь часовой пояс базы данных, не тот из JVM.
Я что-то здесь упускаю?
Как настроить MySQL Connector / J 8.0, чтобы он работал согласованно с другими драйверами JDBC?