日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

SpringBoot时间戳与MySql数据库记录相差14小时排错

發布時間:2023/12/9 javascript 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 SpringBoot时间戳与MySql数据库记录相差14小时排错 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

From: http://www.cnblogs.com/jason1990/archive/2018/11/28/10032181.html

項目中遇到存儲的時間戳與真實時間相差14小時的現象,以下為解決步驟.

問題

CREATE TABLE `incident` (`id` int(11) NOT NULL AUTO_INCREMENT,`created_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,`recovery_time` timestamp NULL DEFAULT NULL,PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4;

以上為數據庫建表語句,其中created_time是插入記錄時自動設置,recovery_time需要手動進行設置.
測試時發現,created_time為正確的北京時間,然而recovery_time則與設置時間相差14小時.

嘗試措施

jvm時區設置

//設置jvm默認時間 System.setProperty("user.timezone", "UTC");

數據庫時區查詢

查看數據庫時區設置:

show variables like '%time_zone%'; --- 查詢結果如下所示: --- system_time_zone: CST --- time_zone:SYSTEM

查詢CST發現其指代比較混亂,有四種含義(參考網址:https://juejin.im/post/5902e087da2f60005df05c3d):

  • 美國中部時間 Central Standard Time (USA) UTC-06:00
  • 澳大利亞中部時間 Central Standard Time (Australia) UTC+09:30
  • 中國標準時 China Standard Time UTC+08:00
  • 古巴標準時 Cuba Standard Time UTC-04:00

此處發現如果按照美國中部時間進行推算,相差14小時,與Bug吻合.

驗證過程

MyBatis轉換

代碼中,時間戳使用Instant進行存儲,因此跟蹤package org.apache.ibatis.type下的InstantTypeHandler.

@UsesJava8 public class InstantTypeHandler extends BaseTypeHandler<Instant> {@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, Instant parameter, JdbcType jdbcType) throws SQLException {ps.setTimestamp(i, Timestamp.from(parameter));}//...代碼shenglve }

調試時發現parameter為正確的UTC時.
函數中調用Timestamp.from將Instant轉換為Timestamp實例,檢查無誤.

/*** Sets the designated parameter to the given <code>java.sql.Timestamp</code> value.* The driver* converts this to an SQL <code>TIMESTAMP</code> value when it sends it to the* database.** @param parameterIndex the first parameter is 1, the second is 2, ...* @param x the parameter value* @exception SQLException if parameterIndex does not correspond to a parameter* marker in the SQL statement; if a database access error occurs or* this method is called on a closed <code>PreparedStatement</code> */void setTimestamp(int parameterIndex, java.sql.Timestamp x)throws SQLException;

繼續跟蹤setTimestamp接口,其具體解釋見代碼注釋.

Sql Driver轉換

項目使用com.mysql.cj.jdbc驅動,跟蹤其setTimestamp在ClientPreparedStatement類下的具體實現(PreparedStatementWrapper類下實現未進入).

@Overridepublic void setTimestamp(int parameterIndex, Timestamp x) throws java.sql.SQLException {synchronized (checkClosed().getConnectionMutex()) {((PreparedQuery<?>) this.query).getQueryBindings().setTimestamp(getCoreParameterIndex(parameterIndex), x);}}

繼續跟蹤上端代碼中的getQueryBindings().setTimestamp()實現(com.mysql.cj.ClientPreparedQueryBindings).

@Overridepublic void 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 positionsfractionalLength = 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);}}

注意此處時區轉換,會調用如下語句獲取默認時區:

this.session.getServerSession().getDefaultTimeZone()

獲取TimeZone數據,具體如下圖所示:

檢查TimeZone類中offset含義,具體如下所示:

/*** Gets the time zone offset, for current date, modified in case of* daylight savings. This is the offset to add to UTC to get local time.* <p>* This method returns a historically correct offset if an* underlying <code>TimeZone</code> implementation subclass* supports historical Daylight Saving Time schedule and GMT* offset changes.** @param era the era of the given date.* @param year the year in the given date.* @param month the month in the given date.* Month is 0-based. e.g., 0 for January.* @param day the day-in-month of the given date.* @param dayOfWeek the day-of-week of the given date.* @param milliseconds the milliseconds in day in <em>standard</em>* local time.** @return the offset in milliseconds to add to GMT to get local time.** @see Calendar#ZONE_OFFSET* @see Calendar#DST_OFFSET*/public abstract int getOffset(int era, int year, int month, int day,int dayOfWeek, int milliseconds);

offset表示本地時間與UTC時的時間間隔(ms).
計算數值offset,發現其表示美國中部時間,即UTC-06:00.

  • Driver推斷Session時區為UTC-6;
  • Driver將Timestamp轉換為UTC-6的String;
  • MySql認為Session時區在UTC+8,將String轉換為UTC+8.

因此,最終結果相差14小時,bug源頭找到.

解決方案

參照https://juejin.im/post/5902e087da2f60005df05c3d.

mysql> set global time_zone = '+08:00'; Query OK, 0 rows affected (0.00 sec)mysql> set time_zone = '+08:00'; Query OK, 0 rows affected (0.00 sec)

告知運維設置時區,重啟MySql服務,問題解決.

此外,作為防御措施,可以在jdbc url中設置時區(如此設置可以不用修改MySql配置):

jdbc:mysql://localhost:3306/table_name?useTimezone=true&serverTimezone=GMT%2B8

此時,就告知連接進行時區轉換,并且時區為UTC+8.

PS:
如果您覺得我的文章對您有幫助,可以掃碼領取下紅包,謝謝!

總結

以上是生活随笔為你收集整理的SpringBoot时间戳与MySql数据库记录相差14小时排错的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。