說來慚愧,幹了 10 來年程式設計師,還沒有給開源做過任何貢獻,以前只知道嘎嘎寫,出了問題嘎嘎改,從來沒想過提個 PR 去修復他,最近碰到個問題,發現挺簡單的,就隨手提了個 PR 過去。
問題挺簡單的,就是在使用 mybatis 和 ShardingSphere 的時候,有人在 model 類使用了 OffsetDateTime
這個時間型別,發現會報錯。
Caused by: java.lang.ClassCastException: class java.sql.Timestamp cannot be cast to class java.time.OffsetDateTime (java.sql.Timestamp is in module java.sql of loader 'platform'; java.time.OffsetDateTime is in module java.base of loader 'bootstrap')
at org.apache.ibatis.type.OffsetDateTimeTypeHandler.getNullableResult(OffsetDateTimeTypeHandler.java:38)
at org.apache.ibatis.type.OffsetDateTimeTypeHandler.getNullableResult(OffsetDateTimeTypeHandler.java:28)
at org.apache.ibatis.type.BaseTypeHandler.getResult(BaseTypeHandler.java:85)
... 99 more
這就是一個簡單的型別轉換的異常,於是跟著原始碼看了下,先看到BaseTypeHandler#getResult
這個方法,實際上就是根據列名返回查詢結果。
根據呼叫關係,找到了OffsetDateTimeTypeHandler
實現類。
發現最終會呼叫rs.getObject()
這個方法,那麼其實這個方法會最終走到由 ShardingSphere 實現的 getObject
方法中。
看到這裡的時候其實已經明白了為啥會報錯了,Shardingsphere 只判斷了幾個LocalDateTime
等型別,對於這個比較特殊的時間型別沒有處理,最終會轉換成 Timestamp ,然後強轉就報錯了。
最後呼叫到ResultSetUtil#convertTimestampValue
方法,可以看到確實是這樣哈。
那如果修改原始碼的話其實很簡單了,getObject
判斷多加一個,convertTimestampValue
再加一個,就這樣很簡單啊。
@Override
public <T> T getObject(final int columnIndex, final Class<T> type) throws SQLException {
if (BigInteger.class.equals(type)) {
return (T) BigInteger.valueOf(getLong(columnIndex));
} else if (Blob.class.equals(type)) {
return (T) getBlob(columnIndex);
} else if (Clob.class.equals(type)) {
return (T) getClob(columnIndex);
} else if (LocalDateTime.class.equals(type) || LocalDate.class.equals(type) || LocalTime.class.equals(type) || OffsetDateTime.class.equals(type)) {
return (T) ResultSetUtil.convertValue(mergeResultSet.getValue(columnIndex, Timestamp.class), type);
} else {
return (T) ResultSetUtil.convertValue(mergeResultSet.getValue(columnIndex, type), type);
}
}
private static Object convertTimestampValue(final Object value, final Class<?> convertType) {
Timestamp timestamp = (Timestamp) value;
if (LocalDateTime.class.equals(convertType)) {
return timestamp.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
}
if (LocalDate.class.equals(convertType)) {
return timestamp.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
}
if (LocalTime.class.equals(convertType)) {
return timestamp.toInstant().atZone(ZoneId.systemDefault()).toLocalTime();
}
if (OffsetDateTime.class.equals(convertType)) {
return timestamp.toInstant().atZone(ZoneId.systemDefault()).toOffsetDateTime();
}
return value;
}
最開始我其實並不想改原始碼,我在想其他的實現方案,搜尋後發現引入一個包就可以解決,也就是 mybatis 的 JSR310 規範。
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-typehandlers-jsr310</artifactId>
<version>1.0.1</version>
</dependency>
他為什麼能解決這個問題?我看了下他的包裡面的程式碼,這不就是加了個 TypeHandler 自己處理了嘛。
再去看了下 OffsetDateTimeTypeHandler
的實現,其實就是自己就解決了,直接給返回OffsetDateTime
,根本不會走到 ShardingSphere 的邏輯裡面去,這也就是他能解決這個問題的原因了。
當然,如果不想那麼麻煩引入一個包,也可以單獨把他拎出來自己去指定一下,這個很簡單,就不多說了。
於是我想,這事情這麼簡單,我不如提個 PR 給官方吧,這裡教下大家怎麼提 PR 。
因為不是咱們的專案,是沒法 push 程式碼的,所以進入到專案,然後fork
,fork 好了以後,直接把專案 clone 下來,然後執行命令。
git remote add upstream https://github.com/apache/shardingsphere.git
通過命令我們可以看到成功了,這樣就 OK了,然後正常拉分支寫程式碼吧。
寫完之後,正常去我們的專案介面提交 PR,然後就可以了。
當然,過程並沒有這麼順利,雖然說只是很簡單的修改。
首先,這個校驗就給我提示錯誤了,第一點叫我不要用 *
號去參照。
這個其實是 IDEA
的鍋,如果參照同一個包下類過多的話,會自動幫我們轉成星號,這個我們可以在Editor-Code Style-Java
,然後找到 Imports 下的這兩個選項,把他們都改成 99 就可以了,防止他自動給我們改成星號。
還有一些其他的比如 if 後面沒跟空格之類的,這是我忘記格式化了!
然後大佬回覆覺得看不下去,這程式碼太噁心了,說我們是不是可以用java.time.temporal.TemporalAccessor
來判斷,不然這麼多時間型別,搞個毛線呢。
然後我就翻譯了一段英文,我也不知道大佬看沒看懂,我告訴他,這個不好整啊,你看這個介面啊,很多亂七八糟的類實現了他,實際上我覺得我們覆蓋常用的一些就行了,其他的特殊時間型別讓他們自己用 TypeHandler 處理吧。
大佬說,嗯當然,沒辦法判斷這個介面那我們也沒轍了,我說那不可就是嘛。
其實,還有很多時間型別他都會報錯的,最好的辦法這個都抽象出來和Mybatis單獨用實現類,不過那樣的話就得大工作了,我太懶了,就這樣。
。。。
時間過去了幾天,看了下他還沒合我程式碼,是不是嫌棄我?