00. 基本問題
0.0 版本: 驅動5.1.47和8.0.17
0.1 MySQL驅動5.1有userLegacyDatetimeCode和userTimezone兩個參數, 8.0沒有
0.2 Java與MySQL間傳遞時間戳的時候, 傳遞的是年月日時分秒, 沒有時區
0.3 MySQL傳遞回來的是: MySQL讀取到底層存儲的時間戳, 按照當前連接(MySQL側)的時區轉為年月日時分秒
0.4 但是, 兩個系統時區可能會不同, userLegacyDatetimeCode和userTimezone就是用來協調時區的
01. MySQL驅動5.1
1.1 數據庫連接在建立時, 會創建一個Calendar對象保存在連接中, 其中保存了連接創建時的時區, 即下文的"連接時區". 見ConnectionImpl#705
1.2 如果配置了serverTimezone,則會將其保存到連接中, 即下文的"配置時區". 見ConnectionImpl#1978
1.3 userLegacyDatetimeCod=true&userTimezone=false, 這是默認情況
1.3.1 此時對應Java和MySQL時區相同
1.3.2 Java接收到MySQL傳遞來的年月日時分秒, 加上"連接時區"創建時間戳java.sql.Timestamp, 見ResultSetImpl#5877和TimeUtil#369
1.4 userLegacyDatetimeCod=true&userTimezone=true&serverTimezone=GMT%2B6
1.4.0 userTimezone=true, 必須在userLegacyDatetimeCod=true時才有效
1.4.1 此時對應二者時區不同
1.4.2 與3.2相同, 先將年月日時分秒+"連接時區", 創建時間戳
1.4.3 再進行時區調整, 調整為"配置時區". 見ResultSetImpl#5877和TimeUtil#160
1.5 userLegacyDatetimeCod=false&serverTimezone=GMT%2B6
1.5.1 此時對應二者時區不同
1.5.2 將年月日時分秒+"配置時區"創建時間戳. 見ResultSetImpl#5874
1.5.3 這也是8.0的處理方式
02. MySQL驅動8.0
2.1 8.0沒有userLegacyDatetimeCode和userTimezone兩個參數
2.2 一定要配置serverTimezone為MySQL運行的時區. 連接建立時會將這個時區存儲到連接中. 見NativeProtocol#2147#2158
2.3 將年月日時分秒+"配置時區"構造時間戳. 見SqlTimestampValueFactory#100. 這里的cal就是在#68根據"配置時區"創建的
03. 代碼跟蹤中的一些關鍵點
版本5.1
連接初始化的過程
- ConnectionImpl#1978 "配置時區"
- ConnectionImpl#705 將當前時區保存到了數據庫連接中
讀取的過程
- MyBatis的各個TypeHandler
- ByteArrayRow#63 拿到字節數組
- ResultSetRow#705 將字節數組轉為字符串
- ResultSetImpl#5729 將字符串分離為年月日時分秒
- ResultSetImpl#5873 對應上文01.5.2
- ResultSetImpl#5877 對應上文01.4.2和01.4.3
- ResultSetImpl#5317 如果Java要返回的是String, 則會在這里將時間戳轉為jvm當前時區下的年月日時分秒
版本8.0
- NativeProtocol#2147#2158 保存"配置時區"
- ByteArrayRow#89 拿到數據庫返回的字節, 大致相當於 01.5.1的ByteArrayRow#63
- MysqlTextValueDecoder#338 解析字節數組, 拿到年月日時分秒並封裝為InternalTimestamp
- SqlTimestampValueFactory#100 也就是上文02.3
- StringValueFactory#94 如果Java要返回的是String, 就直接將InternalTimestamp轉為字符串, 不考慮當前系統時區了, 與5.1的第7條有區別
- AbstractResultSetRow#78
- PropertyKey jdbc url的property的key枚舉類, https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-configuration-properties.html
04. The server time zone value '???DZ?ʱ?' is unrecognized or represents more than one time zone 異常是怎么回事?
在三種情況下會拋出
上文01.4/01.5/02.2情況下未配置serverTimezone是都會拋出
因為, NativeProtocol#2130或者ConnectionImpl#1960拿到數據庫的system_time_zone是亂碼, 也就是select @@system_time_zone 的值
所以要配置serverTimezone為數據庫運行的時區
05. 問題
時間戳傳遞為什么不是一個數字形式的秒/毫秒呢, 而是一個沒有時區的年月日時分秒呢? 還得協調時區, 多復雜呢?