Java與MySQL時(shí)間不一致問題解決
一、問題情況描述
有時(shí)會遇到這樣的問題:MySQL中datetime、timestamp類型的列,Java與MySQL時(shí)間不一致。
在Java的數(shù)據(jù)庫配置url參數(shù)后面加serverTimezone=GMT%2B8,問題就解決了,但具體是什么導(dǎo)致的這一問題呢?
其實(shí),Java與MySQL時(shí)間不一致主要是因?yàn)椋?strong>CST時(shí)區(qū)的混亂問題。
二、CST時(shí)區(qū)混亂
1. CST有四種含義
CST是一個(gè)混亂的時(shí)區(qū),它有四種含義:
美國標(biāo)準(zhǔn)時(shí)間 Central Standard Time (USA):UTC-06:00(或UTC-05:00)
- 夏令時(shí):3月11日至11月7日,使用UTC-05:00
- 冬令時(shí):11月8日至次年3月11日,使用UTC-06:00
澳大利亞標(biāo)準(zhǔn)時(shí)間 Central Standard Time (Australia):UTC+09:30
中國標(biāo)準(zhǔn)時(shí) China Standard Time:UTC+08:00
古巴標(biāo)準(zhǔn)時(shí) Cuba Standard Time:UTC-04:00
CST在Linux、MySQL、Java中的含義:
- 在Linux或MySQL中,CST表示的是:中國標(biāo)準(zhǔn)時(shí)間(UTC+08:00)
- 在Java中,CST表示的是:中央標(biāo)準(zhǔn)時(shí)間(美國標(biāo)準(zhǔn)時(shí)間)(UTC-05:00或UTC-06:00)
Java中CST時(shí)區(qū)的分析:
public static void main(String[] args) { // 完整時(shí)區(qū)ID與時(shí)區(qū)描述:一共628個(gè) String[] ids = TimeZone.getAvailableIDs(); for (String id : ids) { // System.out.println(id+"\t"+TimeZone.getTimeZone(id).getDisplayName()); } // 系統(tǒng)默認(rèn)時(shí)區(qū) TimeZone defaultTimeZone = TimeZone.getDefault(); System.out.println("系統(tǒng)默認(rèn)時(shí)區(qū):"+defaultTimeZone.getID()+"\t"+defaultTimeZone.getDisplayName()); // 北京時(shí)區(qū) TimeZone bjTimeZone = TimeZone.getTimeZone("Asia/Shanghai"); System.out.println("北京時(shí)區(qū):"+bjTimeZone.getID()+"\t"+bjTimeZone.getDisplayName()); // 東京時(shí)區(qū) TimeZone djTimeZone = TimeZone.getTimeZone("Asia/Tokyo"); System.out.println("東京時(shí)區(qū):"+djTimeZone.getID()+"\t"+djTimeZone.getDisplayName()); // CST時(shí)區(qū) TimeZone cstTimeZone = ZoneInfo.getTimeZone("CST"); System.out.println("CST時(shí)區(qū):"+cstTimeZone.getID()+"\t"+cstTimeZone.getDisplayName()); Date date = new Date(0L); System.out.println("時(shí)間戳=0對應(yīng)系統(tǒng)時(shí)間:"+date.toString()); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); sdf.setTimeZone(bjTimeZone);// 設(shè)置北京時(shí)區(qū) System.out.println("時(shí)間戳0對應(yīng)北京時(shí)間:" + sdf.format(date)); sdf.setTimeZone(djTimeZone);// 設(shè)置東京時(shí)區(qū) System.out.println("時(shí)間戳0對應(yīng)東京時(shí)間:" + sdf.format(date)); sdf.setTimeZone(cstTimeZone);// 設(shè)置CST時(shí)區(qū) System.out.println("時(shí)間戳0對應(yīng)CST時(shí)間:" + sdf.format(date)); }
控制臺輸出:
系統(tǒng)默認(rèn)時(shí)區(qū):Asia/Shanghai 中國標(biāo)準(zhǔn)時(shí)間
北京時(shí)區(qū):Asia/Shanghai 中國標(biāo)準(zhǔn)時(shí)間
東京時(shí)區(qū):Asia/Tokyo 日本標(biāo)準(zhǔn)時(shí)間
CST時(shí)區(qū):CST 中央標(biāo)準(zhǔn)時(shí)間
時(shí)間戳=0對應(yīng)系統(tǒng)時(shí)間:Thu Jan 01 08:00:00 CST 1970
時(shí)間戳0對應(yīng)北京時(shí)間:1970-01-01 08:00:00
時(shí)間戳0對應(yīng)東京時(shí)間:1970-01-01 09:00:00
時(shí)間戳0對應(yīng)CST時(shí)間:1969-12-31 18:00:00
由輸出可知:
CST在Java中(TimeZone中的CST)表示的是中央標(biāo)準(zhǔn)時(shí)間(美國標(biāo)準(zhǔn)時(shí)間)
但需注意:Date中的CST是表示的中國標(biāo)準(zhǔn)時(shí)間
時(shí)間戳永遠(yuǎn)指的是UTC/GMT的值,同一時(shí)間戳在不同時(shí)區(qū)表示不同的絕對時(shí)間
中國的時(shí)區(qū)ID為Asia/Shanghai。
2. 什么是時(shí)區(qū)
為了照顧到各地區(qū)的使用方便,又使其他地方的人容易將本地的時(shí)間換算到別的地方時(shí)間上去。有關(guān)國際會議決定將地球表面按經(jīng)線從南到北,劃成24個(gè)區(qū)域,并且規(guī)定相鄰區(qū)域的時(shí)間相差1小時(shí)。
但由于國家常常是跨越多個(gè)時(shí)區(qū)的,為了照顧到行政上的方便,所以通常國家都會定義一個(gè)統(tǒng)一標(biāo)準(zhǔn)際的時(shí)區(qū)來使用,如中國就是統(tǒng)一使用東八區(qū)時(shí)間標(biāo)準(zhǔn)(北京時(shí)間)。
因?yàn)闀r(shí)區(qū)眾多,所以需要一個(gè)標(biāo)準(zhǔn)時(shí)間作為基準(zhǔn):
- 早期基準(zhǔn)是:GMT(格林尼治標(biāo)準(zhǔn)時(shí)間)
- 后來基準(zhǔn)是:UTC(協(xié)調(diào)世界時(shí))
由于地球在它的橢圓軌道里的運(yùn)動(dòng)速度不均勻,這個(gè)時(shí)刻可能和實(shí)際的太陽時(shí)相差16分鐘,地球每天的自轉(zhuǎn)是有些不規(guī)則的,而且正在緩慢減速。所以,GMT(格林尼治標(biāo)準(zhǔn)時(shí)間)已經(jīng)不再適合被作為標(biāo)準(zhǔn)時(shí)間使用。而是UTC(協(xié)調(diào)世界時(shí))是原子時(shí)秒長為基礎(chǔ),更合適。UTC在時(shí)刻上盡量接近于GMT,這兩者幾乎是一樣的。
UTC這套時(shí)間系統(tǒng)被應(yīng)用于許多互聯(lián)網(wǎng)和萬維網(wǎng)的標(biāo)準(zhǔn)中,例如,網(wǎng)絡(luò)時(shí)間協(xié)議就是協(xié)調(diào)世界時(shí)在互聯(lián)網(wǎng)中使用的一種方式。
- 如果本地時(shí)間比UTC時(shí)間快,例如中國大陸的時(shí)間比UTC快8小時(shí),寫作UTC+8(東8區(qū))。
- 如果本地時(shí)間比UTC時(shí)間慢,例如夏威夷的時(shí)間比UTC時(shí)間慢10小時(shí),寫作UTC-10(西10區(qū))。
三、絕對時(shí)間與本地時(shí)間
絕對時(shí)間與本地時(shí)間關(guān)系:絕對時(shí)間 = 本地時(shí)間 & 時(shí)區(qū)偏移量 (AbsoluteTime = LocalDateTime & Offset)
1. 絕對時(shí)間
絕對時(shí)間(AbsoluteTime)是一個(gè)指向絕對時(shí)間線上的一個(gè)確定的時(shí)刻,不受所在地的影響。
UTC時(shí)間就是一個(gè)絕對時(shí)間。
當(dāng)我們記錄一個(gè)時(shí)間為1970-01-01T00:00:00Z(UTC描述時(shí)間的標(biāo)準(zhǔn)格式)時(shí),這個(gè)時(shí)間的定義是沒有任何歧義的,在地球上的任何地方,他們的UTC時(shí)間也一定是相同的。
Unix時(shí)間戳也是一個(gè)絕對時(shí)間。
Unix時(shí)間戳的定義與時(shí)區(qū)無關(guān)。時(shí)間戳是指從絕對時(shí)間點(diǎn)(UTC時(shí)間1970年1月1日午夜)起經(jīng)過的秒數(shù)(或毫秒)。無論您使用什么時(shí)區(qū),時(shí)間戳都代表一個(gè)時(shí)刻,在任何地方都是相同的。
2. 本地時(shí)間
本地時(shí)間(LocalDateTime)是某一時(shí)區(qū)的時(shí)間。
舉例:北京時(shí)間2022-10-10 08:00:00。
- “2022-10-10 08:00:00”是本地時(shí)間(不含時(shí)區(qū)描述)
- “北京時(shí)間2022-10-10 08:00:00”整體是絕對時(shí)間(含時(shí)區(qū)描述)
3. 時(shí)區(qū)偏移量
全球分為24個(gè)時(shí)區(qū),每個(gè)時(shí)區(qū)和零時(shí)區(qū)相差了數(shù)個(gè)小時(shí),也就是這里所說的時(shí)區(qū)偏移量(Offset)。
例如:北京時(shí)間2022-10-10 08:00:00,它本身是一個(gè)絕對時(shí)間,表示成UTC時(shí)間是2020-08-24T03:00:00+08:00
- 其中的2020-08-24T03:00:00是本地時(shí)間
- 其中的+08:00可以看作是時(shí)區(qū)偏移量
時(shí)區(qū)偏移量 = 地區(qū) & 規(guī)則 (Offset = Zone & Rules)
這里的規(guī)則(Rules)可能是一個(gè)變化的值,如果我們單純地認(rèn)為中國的時(shí)區(qū)偏移量是8個(gè)小時(shí),就出錯(cuò)了。
舉例說明:
中國其實(shí)也實(shí)行過夏令時(shí),(1992年之后中國已經(jīng)沒有再實(shí)行過夏令時(shí)了,所以大家對這個(gè)概念并不熟悉)。
- 當(dāng)實(shí)行夏令時(shí),中國標(biāo)準(zhǔn)時(shí)間的時(shí)區(qū)偏移量就是+09:00
- 當(dāng)非夏令時(shí),中國標(biāo)準(zhǔn)時(shí)間的時(shí)區(qū)偏移量就是+08:00
因此,一個(gè)地區(qū)的時(shí)區(qū)偏移量是多少,是由當(dāng)?shù)氐恼邲Q定的,可能會隨著季節(jié)而發(fā)生變化,這就是上面所說的規(guī)則。
四、MySQL服務(wù)端時(shí)區(qū)
MySQL時(shí)區(qū)相關(guān)參數(shù)有兩個(gè):
- system_time_zone(系統(tǒng)時(shí)區(qū))
- time_zone(全局時(shí)區(qū)或當(dāng)前會話時(shí)區(qū))
1. system_time_zone(系統(tǒng)時(shí)區(qū))
在MySQL啟動(dòng)時(shí)會檢查當(dāng)前系統(tǒng)的時(shí)區(qū)并根據(jù)系統(tǒng)時(shí)區(qū)設(shè)置全局參數(shù)system_time_zone的值。值可以為UTC、CST、WIB等,默認(rèn)值一般為CST,該值是只讀的。
2. time_zone(全局時(shí)區(qū)或當(dāng)前會話時(shí)區(qū))
全局時(shí)區(qū):mysql服務(wù)端使用的時(shí)區(qū),可以修改,默認(rèn)值SYSTEM
mysql> show global variables like "%time_zone%"; +------------------+--------+ | Variable_name | Value | +------------------+--------+ | system_time_zone | CST | | time_zone | SYSTEM | +------------------+--------+ 2 rows in set (0.00 sec) mysql> set global time_zone = '+9:00'; Query OK, 0 rows affected (0.00 sec) mysql> show global variables like "%time_zone%"; +------------------+--------+ | Variable_name | Value | +------------------+--------+ | system_time_zone | CST | | time_zone | +09:00 | +------------------+--------+ 2 rows in set (0.00 sec)
此時(shí)查到的time_zone為全局時(shí)區(qū)
mysql> flush privileges; Query OK, 0 rows affected (0.01 sec)
該命令使全局時(shí)區(qū)的修改立即生效,否則只有等mysql服務(wù)重啟才會生效。
會話時(shí)區(qū):當(dāng)前會話的時(shí)區(qū),默認(rèn)取全局時(shí)區(qū)的值,可以修改
mysql> show variables like "%time_zone%"; +------------------+--------+ | Variable_name | Value | +------------------+--------+ | system_time_zone | CST | | time_zone | SYSTEM | +------------------+--------+ 2 rows in set (0.00 sec) mysql> set time_zone = '+9:00'; Query OK, 0 rows affected (0.00 sec) mysql> show variables like "%time_zone%"; +------------------+--------+ | Variable_name | Value | +------------------+--------+ | system_time_zone | CST | | time_zone | +09:00 | +------------------+--------+ 2 rows in set (0.00 sec)
此時(shí)查到的time_zone為當(dāng)前會話時(shí)區(qū)
五、問題具體分析
本文使用的MySQL驅(qū)動(dòng)為cj驅(qū)動(dòng)。
Java通過MySQL的jdbc驅(qū)動(dòng)連接MySQL服務(wù)端:
- 通過jdbc的serverTimezone參數(shù)設(shè)置數(shù)據(jù)庫連接的時(shí)區(qū)。
- 當(dāng)未設(shè)置serverTimezone時(shí),數(shù)據(jù)庫將連接使用MySQL服務(wù)端的time_zone(全局時(shí)區(qū)),默認(rèn)值為CST。time_zone的默認(rèn)值為SYSTEM,而SYSTEM取的是system_time_zone(系統(tǒng)時(shí)區(qū))的值,system_time_zone的默認(rèn)值就是CST。
對于CST,文章上文有提過:
- MySQL中,CST表示的是:中國標(biāo)準(zhǔn)時(shí)間(UTC+08:00)
- Java中,CST表示的是:美國標(biāo)準(zhǔn)時(shí)間(UTC-05:00或UTC-06:00)
由于Java和MySQL服務(wù)端對CST時(shí)區(qū)的不同解讀,最終導(dǎo)致了Java與MySQL時(shí)間不一致的問題。
關(guān)于serverTimezone
分析mysql的jdbc驅(qū)動(dòng)代碼。MySQL驅(qū)動(dòng)創(chuàng)建數(shù)據(jù)庫連接后,會配置此連接的時(shí)區(qū):
- 普通驅(qū)動(dòng):使用com.mysql.jdbc.ConnectionImpl#configureTimezone()配置連接的時(shí)區(qū)
- cj驅(qū)動(dòng):使用com.mysql.cj.protocol.a.NativeProtocol#configureTimezone()配置連接的時(shí)區(qū)
數(shù)據(jù)庫連接時(shí)區(qū)的設(shè)置:
- 如果配置了serverTimezone,則會使用serverTimezone配置的時(shí)區(qū)
- 如果沒配置,會去取數(shù)據(jù)庫中time_zone變量所配置的時(shí)區(qū)
serverTimezone配置的注意事項(xiàng):
- 如果未配置serverTimezone,且數(shù)據(jù)庫time_zone是CST,時(shí)間會不一致
- 如果未配置serverTimezone,但數(shù)據(jù)庫time_zone不是CST(如GMT),時(shí)間一致
- 如果配置了serverTimezone,但與數(shù)據(jù)庫time_zone不是同一時(shí)區(qū),時(shí)間會不一致
- 如果配置了serverTimezone,且與數(shù)據(jù)庫time_zone是同一時(shí)區(qū),時(shí)間一致
你或許會發(fā)現(xiàn)一個(gè)奇怪的事情:貌似我配置的serverTimezone與據(jù)庫time_zone不是同一時(shí)區(qū)。但是Java中的存入時(shí)間和查詢得到的時(shí)間明明是一致且正確的,好像和上面描述得不一樣呀。
這里需要強(qiáng)調(diào)一下,上面所說的時(shí)間不一致是指的Java中的時(shí)間與MySQL數(shù)據(jù)庫中的時(shí)間(并不是Java中的存入時(shí)間和查詢得到的時(shí)間)。
為何Java中的存入時(shí)間和查詢得到的時(shí)間是一致且正確的?
舉個(gè)例子說明:
serverTimezone=+9(東九區(qū)),time_zone=+8:00(東八區(qū)),此時(shí)準(zhǔn)備把Java中的時(shí)間"2022-10-15 08:00:00"存入數(shù)據(jù)庫
- Java存入到MySQL時(shí),誤認(rèn)為MySQL數(shù)據(jù)庫的時(shí)區(qū)是東九區(qū),時(shí)間+1小時(shí),MySQL最終得到時(shí)間為:2022-10-15 09:00:00
- MySQL返回給Java時(shí),誤認(rèn)為MySQL返回的時(shí)間是東九區(qū)的時(shí)間,時(shí)間-1小時(shí),Java最終得到的時(shí)間為:2022-10-15 08:00:00,和正確時(shí)間一致
Java到MySQL的過程,以及MySQL到Java的過程,時(shí)間的處理在MySQL JDBC驅(qū)動(dòng)環(huán)節(jié)。
serverTimezone配置的歸納總結(jié):
- 如果數(shù)據(jù)庫time_zone是CST,請配置serverTimezone=%2B8(+08:00)
- 如果數(shù)據(jù)庫time_zone是GMT(或其它MySQL與Java解析結(jié)果一致的時(shí)區(qū)格式),可以不配置serverTimezone參數(shù)。但如果要配置,請配置與數(shù)據(jù)庫數(shù)據(jù)庫time_zone一致的時(shí)區(qū)
雖然配置的serverTimezone與數(shù)據(jù)庫數(shù)據(jù)庫time_zone時(shí)區(qū)不一致,Java寫入后查詢得到的時(shí)間也是正常的,但MySQL中存的時(shí)間已經(jīng)是錯(cuò)誤的了。
時(shí)間戳與時(shí)區(qū)無關(guān)性
時(shí)間戳:指1970-01-01 00:00:00(GMT/UTC)起到當(dāng)前的毫秒數(shù)。與時(shí)區(qū)無關(guān),不同時(shí)區(qū)同一個(gè)時(shí)刻的時(shí)間戳是相同的。
- 當(dāng)UTC時(shí)區(qū)的時(shí)間為1970-01-01 00:00:00時(shí),時(shí)間戳為0
- 此時(shí)UTC+8(東8區(qū))時(shí)區(qū)的時(shí)間為1970-01-01 08:00:00,時(shí)間戳也為0
- 此時(shí)UTC+9(東9區(qū))時(shí)區(qū)的時(shí)間為1970-01-01 09:00:00,時(shí)間戳也為0
public static void main(String[] args) { Date date = new Date(0L); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); sdf.setTimeZone(TimeZone.getTimeZone("UTC")); System.out.println("時(shí)間戳0對應(yīng)時(shí)間(UTC):"+sdf.format(date)); sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")); System.out.println("時(shí)間戳0對應(yīng)時(shí)間(UTC+8):"+sdf.format(date)); sdf.setTimeZone(TimeZone.getTimeZone("Asia/Tokyo")); System.out.println("時(shí)間戳0對應(yīng)時(shí)間(UTC+9):"+sdf.format(date)); }
時(shí)間戳0對應(yīng)時(shí)間(UTC):1970-01-01 00:00:00
時(shí)間戳0對應(yīng)時(shí)間(UTC+8):1970-01-01 08:00:00
時(shí)間戳0對應(yīng)時(shí)間(UTC+9):1970-01-01 09:00:00
主要步驟流程圖分析
1. 正確情況流程圖
Java系統(tǒng)時(shí)區(qū):Asia/Shanghai(東8區(qū))
JDBC數(shù)據(jù)庫連接時(shí)區(qū):serverTimezone=+8
MySQL全局時(shí)區(qū):time_zone=+08:00
2. 錯(cuò)誤情況流程圖
Java系統(tǒng)時(shí)區(qū):Asia/Shanghai(東8區(qū))
JDBC數(shù)據(jù)庫連接時(shí)區(qū):serverTimezone=-5
MySQL全局時(shí)區(qū):time_zone=+08:00
錯(cuò)誤情況詳細(xì)分析
Java寫入時(shí)間到MySQL服務(wù)端環(huán)節(jié):
Java準(zhǔn)備寫入的時(shí)間為:2022-10-15 08:00:00(UTC+8)
JDBC先轉(zhuǎn)化得到Timestamp:2022-10-15 00:00:00(UTC)
注意:時(shí)間戳記錄的是UTC時(shí)區(qū)的值,與UTC+8時(shí)區(qū)的2022-10-15 08:00:00是同一時(shí)間
JDBC在將Timestamp格式化為UTC-5時(shí)區(qū)(serverTimezone=-5)的時(shí)間字符串:2022-10-14 19:00:00,將字符串傳給MySQL服務(wù)端
MySQL服務(wù)端認(rèn)為2022-10-14 19:00:00就是MySQL全局時(shí)區(qū)time_zone=+08:00(UTC+8)時(shí)區(qū)的時(shí)間,存入。
MySQL服務(wù)端返回時(shí)間給Java環(huán)節(jié):
MySQL服務(wù)端返回UTC+8時(shí)區(qū)的時(shí)間字符串:2022-10-14 19:00:00
JDBC誤認(rèn)為該時(shí)間是UTC-5時(shí)區(qū)(serverTimezone=-5)先將時(shí)間字符串轉(zhuǎn)為Timestamp:2022-10-15 00:00:00(UTC)
Java將Timestamp轉(zhuǎn)化為:2022-10-15 00:00:00(UTC+8)
主要步驟源碼分析
① JDBC配置MySQL服務(wù)時(shí)區(qū)
如果配置了serverTimezone,則會使用serverTimezone配置的時(shí)區(qū)
如果沒配置,會去取數(shù)據(jù)庫中time_zone變量所配置的時(shí)區(qū)
具體方法:NativeProtocol類的configureTimezone方法
public void configureTimezone() { String configuredTimeZoneOnServer = this.serverSession.getServerVariable("time_zone"); if ("SYSTEM".equalsIgnoreCase(configuredTimeZoneOnServer)) { configuredTimeZoneOnServer = this.serverSession.getServerVariable("system_time_zone"); } // 獲取serverTimezone配置的時(shí)區(qū)(PropertyKey.serverTimezone=serverTimezone) String canonicalTimezone = getPropertySet().getStringProperty(PropertyKey.serverTimezone).getValue(); if (configuredTimeZoneOnServer != null) { // 如果沒配置serverTimezone,獲取數(shù)據(jù)庫中time_zone變量的時(shí)區(qū) 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) { // 設(shè)置服務(wù)時(shí)區(qū) this.serverSession.setServerTimeZone(TimeZone.getTimeZone(canonicalTimezone)); if (!canonicalTimezone.equalsIgnoreCase("GMT") && this.serverSession.getServerTimeZone().getID().equals("GMT")) { throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("Connection.9", new Object[] { canonicalTimezone }), getExceptionInterceptor()); } } // 設(shè)置默認(rèn)時(shí)區(qū) this.serverSession.setDefaultTimeZone(this.serverSession.getServerTimeZone()); }
JDBC創(chuàng)建數(shù)據(jù)庫連接就是使用該時(shí)區(qū)。
如果沒配置serverTimezone,獲取數(shù)據(jù)庫中time_zone變量的時(shí)區(qū)為CST,就會有問題,因?yàn)樵趈ava中:TimeZone.getTimeZone("CST")
表示的是中央標(biāo)準(zhǔn)時(shí)間(美國標(biāo)準(zhǔn)時(shí)間)UTC-5(UTC-6)。
CST問題的源頭:
public SqlTimestampValueFactory(PropertySet pset, Calendar calendar, TimeZone tz) { super(pset); if (calendar != null) { this.cal = (Calendar) calendar.clone(); } else { this.cal = Calendar.getInstance(tz, Locale.US); this.cal.setLenient(false); } }
debug結(jié)果:
② Java寫入時(shí)間到MySQL服務(wù)端
ClientPreparedStatement類的setTimestamp方法
@Override public void setTimestamp(int parameterIndex, Timestamp x) throws java.sql.SQLException { synchronized (checkClosed().getConnectionMutex()) { ((PreparedQuery<?>) this.query).getQueryBindings().setTimestamp(getCoreParameterIndex(parameterIndex), x); } }
ClientPreparedQueryBindings類的setTimestamp方法
public 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) { fractionalLength = 6; } x = TimeUtil.adjustTimestampNanosPrecision(x, fractionalLength, !this.session.getServerSession().isServerTruncatesFracSecs()); // 將時(shí)間戳格式化為字符串時(shí)間 // this.session.getServerSession().getDefaultTimeZone() 時(shí)區(qū)(未配置serverTimezone,且數(shù)據(jù)庫中time_zone變量的時(shí)區(qū)為CST時(shí),這里就是CST時(shí)區(qū)) 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); } }
將時(shí)間格式化為字符串時(shí)間(根據(jù)連接的時(shí)區(qū))。
③ MySQL服務(wù)端返回時(shí)間給Java
ResultSetImpl類的getTimestamp方法
public Timestamp getTimestamp(String columnName) throws SQLException { return getTimestamp(findColumn(columnName)); } public Timestamp getTimestamp(int columnIndex) throws SQLException { checkRowPos(); checkColumnBounds(columnIndex); return this.thisRow.getValue(columnIndex - 1, this.defaultTimestampValueFactory); }
SqlTimestampValueFactory類的localCreateFromTimestamp方法
public Timestamp localCreateFromTimestamp(InternalTimestamp its) { if (its.getYear() == 0 && its.getMonth() == 0 && its.getDay() == 0) { throw new DataReadException(Messages.getString("ResultSet.InvalidZeroDate")); } synchronized (this.cal) { try { // 這里就是關(guān)鍵環(huán)節(jié),this.cal是一個(gè)Calendar類,里面有時(shí)區(qū)信息(未配置serverTimezone,且數(shù)據(jù)庫中time_zone變量的時(shí)區(qū)為CST時(shí),這里就是CST時(shí)區(qū)) this.cal.set(its.getYear(), its.getMonth() - 1, its.getDay(), its.getHours(), its.getMinutes(), its.getSeconds()); Timestamp ts = new Timestamp(this.cal.getTimeInMillis()); ts.setNanos(its.getNanos()); return ts; } catch (IllegalArgumentException e) { throw ExceptionFactory.createException(WrongArgumentException.class, e.getMessage(), e); } } }
到此這篇關(guān)于Java與MySQL時(shí)間不一致問題解決的文章就介紹到這了,更多相關(guān)Java與MySQL時(shí)間不一致內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
spring+springmvc+mybatis+maven入門實(shí)戰(zhàn)(超詳細(xì)教程)
這篇文章主要介紹了spring+springmvc+mybatis+maven入門實(shí)戰(zhàn)(超詳細(xì)教程),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-05-05springboot2.6.3讀取不到nacos上的配置文件問題
這篇文章主要介紹了springboot2.6.3讀取不到nacos上的配置文件問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07Spring-retry實(shí)現(xiàn)循環(huán)重試功能
這篇文章主要介紹了Spring-retry 優(yōu)雅的實(shí)現(xiàn)循環(huán)重試功能,通過@Retryable注解,優(yōu)雅的實(shí)現(xiàn)循環(huán)重試功能,需要的朋友可以參考下2023-07-07SpringBoot前后端json數(shù)據(jù)交互的全過程記錄
現(xiàn)在大多數(shù)互聯(lián)網(wǎng)項(xiàng)目都是采用前后端分離的方式開發(fā),下面這篇文章主要給大家介紹了關(guān)于SpringBoot前后端json數(shù)據(jù)交互的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-03-03Spring?Boot:Idea從零開始初始化后臺項(xiàng)目的教程
這篇文章主要介紹了Spring?Boot:Idea從零開始初始化后臺項(xiàng)目的教程,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12Java實(shí)現(xiàn)冒泡排序與雙向冒泡排序算法的代碼示例
這篇文章主要介紹了Java實(shí)現(xiàn)冒泡排序與雙向冒泡排序算法的代碼示例,值得一提的是所謂的雙向冒泡排序并不比普通的冒泡排序效率來得高,注意相應(yīng)的時(shí)間復(fù)雜度,需要的朋友可以參考下2016-04-04SpringMVC中的handlerMappings對象用法
這篇文章主要介紹了SpringMVC中的handlerMappings對象用法,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09詳解SpringBoot項(xiàng)目的創(chuàng)建與單元測試
這篇文章主要介紹了詳解SpringBoot項(xiàng)目的創(chuàng)建與單元測試,幫助大家更好的理解和學(xué)習(xí)使用SpringBoot,感興趣的朋友可以了解下2021-03-03