Java與MySQL時(shí)間不一致問題解決
一、問題情況描述
有時(shí)會(huì)遇到這樣的問題: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對(duì)應(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對(duì)應(yīng)北京時(shí)間:" + sdf.format(date));
sdf.setTimeZone(djTimeZone);// 設(shè)置東京時(shí)區(qū)
System.out.println("時(shí)間戳0對(duì)應(yīng)東京時(shí)間:" + sdf.format(date));
sdf.setTimeZone(cstTimeZone);// 設(shè)置CST時(shí)區(qū)
System.out.println("時(shí)間戳0對(duì)應(yīng)CST時(shí)間:" + sdf.format(date));
}
控制臺(tái)輸出:
系統(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對(duì)應(yīng)系統(tǒng)時(shí)間:Thu Jan 01 08:00:00 CST 1970
時(shí)間戳0對(duì)應(yīng)北京時(shí)間:1970-01-01 08:00:00
時(shí)間戳0對(duì)應(yīng)東京時(shí)間:1970-01-01 09:00:00
時(shí)間戳0對(duì)應(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ū)表示不同的絕對(duì)時(shí)間
中國的時(shí)區(qū)ID為Asia/Shanghai。
2. 什么是時(shí)區(qū)
為了照顧到各地區(qū)的使用方便,又使其他地方的人容易將本地的時(shí)間換算到別的地方時(shí)間上去。有關(guān)國際會(huì)議決定將地球表面按經(jīng)線從南到北,劃成24個(gè)區(qū)域,并且規(guī)定相鄰區(qū)域的時(shí)間相差1小時(shí)。
但由于國家常常是跨越多個(gè)時(shí)區(qū)的,為了照顧到行政上的方便,所以通常國家都會(huì)定義一個(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ū))。
三、絕對(duì)時(shí)間與本地時(shí)間
絕對(duì)時(shí)間與本地時(shí)間關(guān)系:絕對(duì)時(shí)間 = 本地時(shí)間 & 時(shí)區(qū)偏移量 (AbsoluteTime = LocalDateTime & Offset)
1. 絕對(duì)時(shí)間
絕對(duì)時(shí)間(AbsoluteTime)是一個(gè)指向絕對(duì)時(shí)間線上的一個(gè)確定的時(shí)刻,不受所在地的影響。
UTC時(shí)間就是一個(gè)絕對(duì)時(shí)間。
當(dāng)我們記錄一個(gè)時(shí)間為1970-01-01T00:00:00Z(UTC描述時(shí)間的標(biāo)準(zhǔn)格式)時(shí),這個(gè)時(shí)間的定義是沒有任何歧義的,在地球上的任何地方,他們的UTC時(shí)間也一定是相同的。
Unix時(shí)間戳也是一個(gè)絕對(duì)時(shí)間。
Unix時(shí)間戳的定義與時(shí)區(qū)無關(guān)。時(shí)間戳是指從絕對(duì)時(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”整體是絕對(duì)時(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è)絕對(duì)時(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í)了,所以大家對(duì)這個(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定的,可能會(huì)隨著季節(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)前會(huì)話時(shí)區(qū))
1. system_time_zone(系統(tǒng)時(shí)區(qū))
在MySQL啟動(dòng)時(shí)會(huì)檢查當(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)前會(huì)話時(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ù)重啟才會(huì)生效。
會(huì)話時(shí)區(qū):當(dāng)前會(huì)話的時(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)前會(huì)話時(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。
對(duì)于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ù)端對(duì)CST時(shí)區(qū)的不同解讀,最終導(dǎo)致了Java與MySQL時(shí)間不一致的問題。
關(guān)于serverTimezone
分析mysql的jdbc驅(qū)動(dòng)代碼。MySQL驅(qū)動(dòng)創(chuàng)建數(shù)據(jù)庫連接后,會(huì)配置此連接的時(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,則會(huì)使用serverTimezone配置的時(shí)區(qū)
- 如果沒配置,會(huì)去取數(shù)據(jù)庫中time_zone變量所配置的時(shí)區(qū)
serverTimezone配置的注意事項(xiàng):
- 如果未配置serverTimezone,且數(shù)據(jù)庫time_zone是CST,時(shí)間會(huì)不一致
- 如果未配置serverTimezone,但數(shù)據(jù)庫time_zone不是CST(如GMT),時(shí)間一致
- 如果配置了serverTimezone,但與數(shù)據(jù)庫time_zone不是同一時(shí)區(qū),時(shí)間會(huì)不一致
- 如果配置了serverTimezone,且與數(shù)據(jù)庫time_zone是同一時(shí)區(qū),時(shí)間一致
你或許會(huì)發(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,請(qǐng)配置serverTimezone=%2B8(+08:00)
- 如果數(shù)據(jù)庫time_zone是GMT(或其它MySQL與Java解析結(jié)果一致的時(shí)區(qū)格式),可以不配置serverTimezone參數(shù)。但如果要配置,請(qǐng)配置與數(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對(duì)應(yīng)時(shí)間(UTC):"+sdf.format(date));
sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
System.out.println("時(shí)間戳0對(duì)應(yīng)時(shí)間(UTC+8):"+sdf.format(date));
sdf.setTimeZone(TimeZone.getTimeZone("Asia/Tokyo"));
System.out.println("時(shí)間戳0對(duì)應(yīng)時(shí)間(UTC+9):"+sdf.format(date));
}
時(shí)間戳0對(duì)應(yīng)時(shí)間(UTC):1970-01-01 00:00:00
時(shí)間戳0對(duì)應(yīng)時(shí)間(UTC+8):1970-01-01 08:00:00
時(shí)間戳0對(duì)應(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,則會(huì)使用serverTimezone配置的時(shí)區(qū)
如果沒配置,會(huì)去取數(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,就會(huì)有問題,因?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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實(shí)現(xiàn)List反轉(zhuǎn)的方法總結(jié)
在Java中,反轉(zhuǎn)一個(gè)List意味著將其元素的順序顛倒,使得第一個(gè)元素變成最后一個(gè),最后一個(gè)元素變成第一個(gè),依此類推,這一操作在處理數(shù)據(jù)集合時(shí)非常有用,所以本文給大家總結(jié)了Java實(shí)現(xiàn)List反轉(zhuǎn)的方法,需要的朋友可以參考下2024-04-04
Java Web 實(shí)現(xiàn)QQ登錄功能一個(gè)帳號(hào)同一時(shí)間只能一個(gè)人登錄
對(duì)于一個(gè)帳號(hào)在同一時(shí)間只能一個(gè)人登錄,下文給大家介紹的非常詳細(xì),對(duì)java web qq 登錄功能感興趣的朋友一起看看吧2016-11-11
Java函數(shù)式編程之通過行為參數(shù)化傳遞代碼
行為參數(shù)化就是可以幫助你處理頻繁變更的需求的一種軟件開發(fā)模式,這篇文章將給大家詳細(xì)的介紹一下Java函數(shù)式編程之行為參數(shù)化傳遞代碼,感興趣的同學(xué)可以參考閱讀下2023-08-08
Java實(shí)現(xiàn)矩陣加減乘除及轉(zhuǎn)制等運(yùn)算功能示例
這篇文章主要介紹了Java實(shí)現(xiàn)矩陣加減乘除及轉(zhuǎn)制等運(yùn)算功能,結(jié)合實(shí)例形式總結(jié)分析了java常見的矩陣運(yùn)算實(shí)現(xiàn)技巧,需要的朋友可以參考下2018-01-01
線程池調(diào)用kafka發(fā)送消息產(chǎn)生的內(nèi)存泄漏問題排查解決
這篇文章主要為大家介紹了線程池調(diào)用kafka發(fā)送消息產(chǎn)生的內(nèi)存泄漏問題排查解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
Spring Cloud 中@FeignClient注解中的contextId屬性詳解
這篇文章主要介紹了Spring Cloud 中@FeignClient注解中的contextId屬性詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09

