DBUtils 如何處理 oracle.sql.TIMESTAMP

接續上篇 java.sql.Date v.s. java.sql.Timestamp in Oracle 9i

Oracle JDBC 的 FAQ 中提到,在 Oracle 9i 中如果要取得時間部分的資料,最好也最標準的方法是將資料型態由 DATE 改為 TIMESTAMP。經過實際測試將資料型態改為 TIMESTAMP 之後,Oracle 9i JDBC Driver 並非如預期地將 TIMESTAMP 轉為 java.sql.Timestamp,而是轉型為 oracle.sql.TIMESTAMP,應用程式必須再呼叫一次 oracle.sql.TIMESTAMP.timestampValue(),才能正確轉回 java.sql.Timestamp,這對我原先已經撰寫完成的許多程式來說,必須一一修改,相當麻煩。

所幸原先程式係透過一個共用元件來使用 DBUtils,經過檢視 Jakarta Commons DBUtils 的原始碼後,問題出在 org.apache.commons.dbutils.BasicRowProcessor 的 toMap() 函式

1
2
3
4
5
6
7
8
9
10
11
12
13


    public Map toMap(ResultSet rs) throws SQLException {
        Map result = new CaseInsensitiveHashMap();
        ResultSetMetaData rsmd = rs.getMetaData();
        int cols = rsmd.getColumnCount();

        for (int i = 1; i <= cols; i++) {
            result.put(rsmd.getColumnName(i), rs.getObject(i));
        }

        return result;
    }

裡面使用 ResultSet.getObject() 來直接取得 JDBC Driver 回傳的型態,而 Oracle 9i JDBC Driver 遇到資料型態為 DATE,則回傳 java.sql.Date,資料型態為 TIMESTAMP,則回傳 oracle.sql.TIMESTAMP。真不知道是 Oracle 的 bug 還是故意這麼搞的,所以 DBUtils 得到的就絕對不會是 java.sql.Timestamp

思考之後,最好的方式應該是寫一個 Wrapper 去繼承 org.apache.commons.dbutils.BasicRowProcessor,然後只改寫 toMap()函式如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32


public class BasicRowProcessorWrapper extends BasicRowProcessor {
    /**
     * 繼承自 org.apache.commons.dbutils.BasicRowProcessor
     * 由於 Oracle 9i JDBC Driver 遇到資料型態為 DATE 會轉為 java.sql.Date
     * 導致僅留下日期而遺失時間的部分,遇到資料型態為 TIMESTAMP 會轉為 oracle.sql.TIMESTAMP
     * 必須自行再轉為 java.sql.Timestamp,因此改寫 toMap() 函式
     * @param rs ResultSet
     * @return Map
     * @throws SQLException
     */
    public Map toMap(ResultSet rs) throws SQLException {
        Map result = new CaseInsensitiveHashMap();
        ResultSetMetaData rsmd = rs.getMetaData();
        int cols = rsmd.getColumnCount();

        for (int i = 1; i <= cols; i++) {
            Object obj = rs.getObject(i);
            if ("oracle.sql.TIMESTAMP".equals(obj.getClass().getName())) {
                result.put(rsmd.getColumnName(i), rs.getTimestamp(i));
            } else if (obj instanceof java.sql.Date) {
                result.put(rsmd.getColumnName(i), rs.getTimestamp(i));
            } else  {
                result.put(rsmd.getColumnName(i), obj);
            }
        }

        return result;
    }
}

遇到 JDBC Driver 型態為 oracle.sql.TIMESTAMP 或是 java.sql.Date,則強迫使用 getTimestamp() 函式來轉型為 java.sql.Timestamp,如此一來,即使未來 DBUtils 改版更新,也無須更動舊有程式

下載相關原始碼

One thought on “DBUtils 如何處理 oracle.sql.TIMESTAMP

  • 2016/12/02 at 10:52:05
    Permalink

    感謝,正好遇到這樣的問題

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料