mysql server和client之间使用string而非long传递时间戳,因此这个string显然是经过时区转换得到的,在这个过程中有三个地方设置了时区:
1.mysql中的time_zone属性
2.jdbc.url中的userServerTimezone属性
3.jvm中的user.timezone属性
在jdbc.url中使用了useLegacyDatetimeCode=false后,mysql设置timestamp的核心代码如下:
private void newSetTimestampInternal(int parameterIndex, Timestamp x, Calendar targetCalendar) throws SQLException {
synchronized (checkClosed().getConnectionMutex()) {
if (this.tsdf == null) {
this.tsdf = new SimpleDateFormat("''yyyy-MM-dd HH:mm:ss", Locale.US);
}
if (targetCalendar != null) {
this.tsdf.setTimeZone(targetCalendar.getTimeZone());
} else {
this.tsdf.setTimeZone(this.connection.getServerTimezoneTZ());
}
StringBuffer buf = new StringBuffer();
buf.append(this.tsdf.format(x));
buf.append('.');
buf.append(TimeUtil.formatNanos(x.getNanos(), this.serverSupportsFracSecs, true));
buf.append('\'');
setInternal(parameterIndex, buf.toString());
}
}
mysql会将timestamp字段转换为特定时区下的string,而这个特定时区有两个选择,targetCalendar字段不为null时,选择targetCalendar中的时区信息,该字段可由PreparedStatement类的setTimestamp方法传入;否则选择serverTimezoneTZ字段的值,设置该字段的代码如下:
private void configureTimezone() throws SQLException {
String configuredTimeZoneOnServer = this.serverVariables.get("timezone");
if (configuredTimeZoneOnServer == null) {
configuredTimeZoneOnServer = this.serverVariables.get("time_zone");
if ("SYSTEM".equalsIgnoreCase(configuredTimeZoneOnServer)) {
configuredTimeZoneOnServer = this.serverVariables.get("system_time_zone");
}
}
//获取jdbc.url中serverTimezone的值
String canonicalTimezone = getServerTimezone();
if ((getUseTimezone() || !getUseLegacyDatetimeCode()) && configuredTimeZoneOnServer != null) {
// user can override this with driver properties, so don't detect if that's the case
if (canonicalTimezone == null || StringUtils.isEmptyOrWhitespaceOnly(canonicalTimezone)) {
try {
canonicalTimezone = TimeUtil.getCanonicalTimezone(configuredTimeZoneOnServer, getExceptionInterceptor());
} catch (IllegalArgumentException iae) {
throw SQLError.createSQLException(iae.getMessage(), SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
}
}
}
if (canonicalTimezone != null && canonicalTimezone.length() > 0) {
this.serverTimezoneTZ = TimeZone.getTimeZone(canonicalTimezone);
if (!canonicalTimezone.equalsIgnoreCase("GMT") && this.serverTimezoneTZ.getID().equals("GMT")) {
throw SQLError.createSQLException("No timezone mapping entry for '" + canonicalTimezone + "'", SQLError.SQL_STATE_ILLEGAL_ARGUMENT,
getExceptionInterceptor());
}
this.isServerTzUTC = !this.serverTimezoneTZ.useDaylightTime() && this.serverTimezoneTZ.getRawOffset() == 0;
}
}
serverTimezoneTZ首先去找jdbc.url中的serverTimezone,若找不到再找mysql server中time_zone属性的值。注意当time_zone为"SYSTEM"时,意为与system_time_zone的值保持一致
从上述分析可知:jvm中的user.timezone属性并不影响mysql server与client之间的传值。
该值其实影响的是controller和前端(外界)之间的传值,在这里不做讨论
为了验证剩下两个属性的作用,做如下测试:
1.client向server发送数据
private Connection connection;
@Before
public void init() throws Exception {
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?characterEncoding" +
"=UTF-8&useLegacyDatetimeCode=false&serverTimezone=UTC", "root", "12345678");
}
@Test
public void test() throws SQLException {
//mysql表中time_stamp字段类型为timestamp
String insert = "INSERT INTO mysql_time_test(time_stamp) VALUES (?)";
PreparedStatement preparedStatement = connection.prepareStatement(insert);
System.out.println(System.currentTimeMillis());
preparedStatement.setTimestamp(1,new Timestamp(System.currentTimeMillis()));
preparedStatement.executeUpdate();
}
@After
public void destory() throws SQLException {
connection.close();
}
在serverTimezone=UTC,time_zone=’+08:00‘的条件下
控制台(client)输出的当前时间戳为1545630400778,对应UTC时区下的“24/12/2018 05:46:40”
mysql表(server)中time_stamp字段存储的时间戳(使用unix_timestamp函数获得)为1545601601,对应于’+08:00‘时区下的“24/12/2018 05:46:41”
mysql client将当前时间戳按照UTC时区转成string发送到mysql server,而mysql server将收到的string用’+08:00‘解析成时间戳进行保存。(两端解析出来的时间string总是差一秒,原因有待考证)
2.client从server拉取数据
将上面insert代码改为select:
String get = "select time_stamp from mysql_time_test order by create_time desc limit 1";
ResultSet resultSet = connection.createStatement().executeQuery(get);
if(resultSet.next()) {
System.out.println(resultSet.getTimestamp(1).getTime());
}
在serverTimezone=UTC,time_zone=’+08:00‘的条件下
控制台(client)输出的当前时间戳为1545667055000,对应UTC时区下的“24/12/2018 15:57:35”
mysql表(server)中time_stamp字段存储的时间戳(使用unix_timestamp函数获得)为1545638255,对应’+08:00‘时区下的“24/12/2018 15:57:35”
mysql server将保存的时间戳按照’+08:00‘时区转成string发送到mysql client,而mysql client将收到的string用UTC时区解析成时间戳。
将以上mysql表字段类型由timestamp改成datetime,发现datetime与timestamp的不同之处在于,从server中拉取的时间string不受time_zone的影响,不论插入数据后如何修改time_zone,返回的时间string始终保持不变。
需要注意的是,虽然server端在处理datetime类型的字段时不受mysql中time_zone的影响,但是若使用java.sql.Timestamp类型接收datetime类型的值,且jdbc.url中未明确定义serverTimezone的值,那么client在收到server发来时间string之后,还是会将其按照time_zone所设置的时区转换为时间戳。