疑云初現
??"1983年小巷,12月晴朗,夜的第七章,打字機繼續推向,接近事實的那下一行"
??耳邊傳來動聽的音樂聲,雙眼微瞇,本·福爾摩斯正沉浸在音樂描繪的場景中,忽然耳邊響起一陣叮鈴叮鈴的清脆響聲,原來是電話響了,本·福爾摩斯立馬回過神來,"看來是案子上門了",本·福爾摩斯心想,然后接起電話,電話的那頭,急切的聲音傳來:
??"喂,請問是本·福爾摩斯探長嗎?我們這里發生了一件奇怪的案子,需要您來查探一下",
??掛斷電話,本·福爾摩斯火速趕往現場,抵達現場后,聯系福爾摩斯的小兄弟立馬匯報了情況,"探長,我們這里出現了一件奇怪的案子,是這樣的,我們有兩個應用,使用的都是同一個資料庫,查同樣的表的同樣的欄位,但是兩個應用的介面回傳的結果截然不同,其中一個介面回傳的是正確的時間戳,另一個介面回傳的是錯誤的時間戳,并且兩個時間戳時差為14小時"
??本·福爾摩斯捋了一捋,"哦?這么奇怪?我來看一看呢",然后福爾摩斯開始還原現場,首先查看了資料庫:

??然后查看了正確的回傳:

??換算成時間是:

??和資料庫相符,再來看一下錯誤的回傳:

??換算成時間是:

??可以看到兩者之間的確差了14個小時,并且是錯誤的資料比正確的資料要晚14個小時,此時本·福爾摩斯陷入了深深的沉思...
疑云叢叢
??深深的吸了一口氣,本·福爾摩斯在腦海中列出了自己的疑問:
??1."為什么兩者查的是同一個庫同一張表甚至同一個欄位,結果會不一樣呢?"
??2."就算不一樣,那為何不是差12小時,13小時,15小時,偏偏是14小時呢?"
?? 本·福爾摩斯百思不得其解,他一步一步的腦海中重復這個程序,"到底是哪里不一樣呢?肯定是有哪里不一樣!",忽然本·福爾摩斯靈光一閃,想到這兩者的共同點都是接收了Mybatis服務員提供的服務,而只有Mybatis在服務程序中會接觸到這個欄位,并且將其轉換,"難道是Mybatis動了手腳?那為什么之前都沒問題呢?",本·福爾摩斯很快否定了Mybatis動了手腳,但是除了Mybatis,也沒有其他人可以動手腳了啊?一時間,案子陷入了層層迷霧中,毫無進展,
??"看來還是得從Mybatis入手,他肯定隱瞞了一些我們不知道的事",心里如是想,于是本·福爾摩斯找到了Mybatis,打算和Mybatis深入談談,但是Mybatis對本·福爾摩斯的疑問矢口否認,Mybatis脾氣暴躁的說:"我說了我沒有做任何手腳!我沒有!",一時間,本·福爾摩斯和Mybatis陷入了一種無言的氛圍,忽然,Mybatis說:"咦,對了,我其實一直都比較懶,對createTime的查詢這些我都委托給JDBC去處理了,處理完了我只是負責把對應的值轉換為對應的型別,會不會是JDBC對createTime動了手腳?",
??本·福爾摩斯精光一閃,"你為什么不早說呢?",然后匆匆跑去查看監控,在命名為DefaultResultSetHandler的監控里,終于在快進到getPropertyMappingValue時驗證了Mybatis的說法:

??再跟隨監控進入DateTypeHandler查看:

??看到這里,本·福爾摩斯自信一笑,他感覺正在接近事實的真相:

??"原來是它,ResultSet!",沒錯,它就是當初本·福爾摩斯和JDBC大戰時,經常會遇到的對手,
大名鼎鼎的ResultSet結果集,"那它又是怎樣神不知鬼不覺的把資料替換掉的呢?"本·福爾摩斯繼續查看,
真相大白
??跟著監控層層深入,本·福爾摩斯來到了犯案的第一現場:

??本·福爾摩斯注意到,這里有一個stringConverter,包含一個ValueFactory屬性:

??而這個targetVf的具體型別為YearToDateValueFactory:

??YearToDateValueFactory也有一個targetVf,其具體的實體型別為:

??然后會呼叫其createFromTimestamp方法:

??這里放大監控看一下:

??原來如此,原來真的是JDBC做了手腳,把時間改了,我們從資料庫正確獲取到了時間是
2021-02-22 11:48:04,但是JDBC呼叫了calendar.getTimeInMills()方法,在該方法中把時間改成了
2021-02-23 11:48:04,正好差了14個小時,再繼續查看監控:

??原來這里,isTimeSet標志位為false,所以要更新時間,至于這個更新時間的程序,比較復雜,本·福爾摩斯為了照顧各位看官的感受,直接講原因,首先來看SqlTimestampValueFactory的構造方法:

??初始化時,calendar為null,會根據傳入的TimeZone去初始化一個Calendar,此時TimeZone為CST,Locale為US,直接說,這里的TimeZone是Mybatis從資料庫的默認時區獲取的,我們使用Mysql:

??很好理解,Mysql使用的時區是隨系統時區,而對應的系統時區為CST,因此Mysql默認會使用CST時區,這就是為什么SqlTimestampValueFactory構造方法中傳入的TimeZone為CST,而CST時區又是一個神奇的時區:
美國中部時間:Central Standard Time (USA) UT-6:00
澳大利亞中部時間:Central Standard Time (Australia) UT+9:30
中國標準時間:China Standard Time UT+8:00
古巴標準時間:Cuba Standard Time UT-4:00
??可以看到,CST能表示4個時區,而JDK8原始碼中默認設定的Locale為US,則代表SqlTimestampValueFactory中的Calendar代表的時間為美國中部西六區的時間,而在SqlTimestampValueFactory.createFromTimestamp方法中呼叫了該calendar實體getTimeInMillis方法:

??而該方法呼叫updateTime(),又呼叫computeTime()方法,其中有這么一行代碼:

??computeTime方法的整個邏輯就是,獲取程式運行的機器的默認時區(一般我們都是東八區Asia/Shanghai),然后根據該時區,更新之前的calendar代表的時間,而根據之前的分析我們知道,此時的calendar代表的時區是美國中部時間西六區,而現在需要更新為中國標準時間,以英國格林威治時間為標準0時,美國西六區-6,而中國處于東八區+8,因此兩個時間差為14小時,并且是程式得到的時間晚于資料庫時間14小時,
??"那為什么另外一個程式卻沒有問題呢?"此時,報案的小兄弟提出了一個疑問
??"很簡單,因為這兩個程式使用的mysql-connector-java的版本不同"


??早期版本(版本號為mysql-connector-java:5.x及以前)的代碼實作中獲取的calendar默認就是系統當前時區,不存在不同的情況,自然不會影響最后的結果:


??如果是mysql-connector-java:6.x 及以上的版本,則會出現這種時差問題,另外mysql-connector-java:5.x 和mysql-connector-java:6.x的一個顯著區別是,配置的資料庫的driverName不同:
driverClassName: com.mysql.jdbc.Driver # mysql-connector-java 5.x及之前版本中的
driverClassName: com.mysql.cj.jdbc.Driver # mysql-connector-java 6.x及后續版本中的
華麗謝幕
??"不愧是本·福爾摩斯探長,手到擒來,那我們下次再遇到這種情況應該如何解決呢?"
??"很簡單,既然我們知道了差14小時的原因,我們就能對癥下藥了,具體根據實際情況選擇"
??1.修改程式的資料庫連接串,指定時區:
??> &serverTimezone=GMT%2B8
??2.修改資料庫的時區,使資料庫時區和程式運營時的默認時區相同:
??> set time_zone = '+8:00';
??"如果邪惡 是華麗殘酷的樂章 它的終場 我會 親手寫上"
??"晨曦的光 風干最后一行憂傷 黑色的墨 染上安詳"
??伴隨著音樂,福爾摩斯悠悠睡去,靜待下一樁案件的到來....
本文由博客一文多發平臺 OpenWrite 發布!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/265060.html
標籤:其他
