給ShardingSphere提了個PR,不知道是不是嫌棄我

說來慚愧,幹了 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 給官方吧,這裡教下大家怎麼提 PR 。

因為不是咱們的項目,是沒法 push 程式碼的,所以進入到項目,然後fork,fork 好了以後,直接把項目 clone 下來,然後執行命令。

git remote add upstream //github.com/apache/shardingsphere.git

通過命令我們可以看到成功了,這樣就 OK了,然後正常拉分支寫程式碼吧。

寫完之後,正常去我們的項目介面提交 PR,然後就可以了。

麻煩

當然,過程並沒有這麼順利,雖然說只是很簡單的修改。

首先,這個校驗就給我提示錯誤了,第一點叫我不要用 *號去引用。

這個其實是 IDEA的鍋,如果引用同一個包下類過多的話,會自動幫我們轉成星號,這個我們可以在Editor-Code Style-Java,然後找到 Imports 下的這兩個選項,把他們都改成 99 就可以了,防止他自動給我們改成星號。

還有一些其他的比如 if 後面沒跟空格之類的,這是我忘記格式化了!

然後大佬回復覺得看不下去,這程式碼太噁心了,說我們是不是可以用java.time.temporal.TemporalAccessor來判斷,不然這麼多時間類型,搞個毛線呢。

然後我就翻譯了一段英文,我也不知道大佬看沒看懂,我告訴他,這個不好整啊,你看這個介面啊,很多亂七八糟的類實現了他,實際上我覺得我們覆蓋常用的一些就行了,其他的特殊時間類型讓他們自己用 TypeHandler 處理吧。

大佬說,嗯當然,沒辦法判斷這個介面那我們也沒轍了,我說那不可就是嘛。

其實,還有很多時間類型他都會報錯的,最好的辦法這個都抽象出來和Mybatis單獨用實現類,不過那樣的話就得大工作了,我太懶了,就這樣。

。。。

時間過去了幾天,看了下他還沒合我程式碼,是不是嫌棄我?