Mysql 時間差了 14 或 13 小時 com.mysql.cj.jdbc.Driver
阿新 • • 發佈:2019-06-19
檢視mysql當前使用時區
show variables like '%time_zone%';
解決辦法
serverTimezone=CTT
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://xxx.xxx.xxx.xxx:3306/shys?serverTimezone=CTT&useUnicode=true&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false spring.datasource.username=xxxxxx spring.datasource.password=xxxxxx spring.datasource.initialize=false
排錯過程
mysql-connector-java-8.0.13.jar
當 JDBC 與 MySQL 開始建立連線時
com.mysql.cj.jdbc.ConnectionImpl.initializePropsFromServer() throws SQLException { ... this.session.getProtocol().initServerSession(); ... } com.mysql.cj.protocol.a.NativeProtocol.initServerSession() { configureTimezone(); ... } com.mysql.cj.protocol.a.NativeProtocol.configureTimezone() { String configuredTimeZoneOnServer = this.serverSession.getServerVariable("time_zone"); if ("SYSTEM".equalsIgnoreCase(configuredTimeZoneOnServer)) { configuredTimeZoneOnServer = this.serverSession.getServerVariable("system_time_zone"); } String canonicalTimezone = getPropertySet().getStringProperty(PropertyKey.serverTimezone).getValue(); if (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 ExceptionFactory.createException(WrongArgumentException.class, iae.getMessage(), getExceptionInterceptor()); } } } if (canonicalTimezone != null && canonicalTimezone.length() > 0) { this.serverSession.setServerTimeZone(TimeZone.getTimeZone(canonicalTimezone)); // // The Calendar class has the behavior of mapping unknown timezones to 'GMT' instead of throwing an exception, so we must check for this... // if (!canonicalTimezone.equalsIgnoreCase("GMT") && this.serverSession.getServerTimeZone().getID().equals("GMT")) { throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("Connection.9", new Object[] { canonicalTimezone }), getExceptionInterceptor()); } } this.serverSession.setDefaultTimeZone(this.serverSession.getServerTimeZone()); }
追蹤程式碼可知,當 MySQL 的 time_zone
值為 SYSTEM
時,會取 system_time_zone
值作為協調時區。
重點在這裡!若 String configuredTimeZoneOnServer
得到的是 CST
那麼 Java 會誤以為這是 CST -0500
,因此 TimeZone.getTimeZone(canonicalTimezone)
會給出錯誤的時區資訊。
如圖所示,本機預設時區是 Asia/Shanghai +0800
,誤認為伺服器時區為 CST -0500
,實際上伺服器是 CST +0800
。
com.mysql.cj.ClientPreparedQueryBindings.setTimestamp(int parameterIndex, Timestamp x, Calendar targetCalendar, int fractionalLength) { if (x == null) { setNull(parameterIndex); } else { x = (Timestamp) x.clone(); if (!this.session.getServerSession().getCapabilities().serverSupportsFracSecs() || !this.sendFractionalSeconds.getValue() && fractionalLength == 0) { x = TimeUtil.truncateFractionalSeconds(x); } if (fractionalLength < 0) { // default to 6 fractional positions fractionalLength = 6; } x = TimeUtil.adjustTimestampNanosPrecision(x, fractionalLength, !this.session.getServerSession().isServerTruncatesFracSecs()); this.tsdf = TimeUtil.getSimpleDateFormat(this.tsdf, "''yyyy-MM-dd HH:mm:ss", targetCalendar, targetCalendar != null ? null : this.session.getServerSession().getDefaultTimeZone()); StringBuffer buf = new StringBuffer(); buf.append(this.tsdf.format(x)); if (this.session.getServerSession().getCapabilities().serverSupportsFracSecs()) { buf.append('.'); buf.append(TimeUtil.formatNanos(x.getNanos(), 6)); } buf.append('\''); setValue(parameterIndex, buf.toString(), MysqlType.TIMESTAMP); } }
原因
Timestamp 被轉換為會話時區的時間字串了。問題到此已然明晰:
- JDBC 誤認為會話時區在 CST-5
- JBDC 把 Timestamp+0 轉為 CST-5 的 String-5
- MySQL 認為會話時區在 CST+8,將 String-5 轉為 Timestamp-13
最終結果相差 13 個小時!如果處在冬令時還會相差 14 個小時!