Spring Data JPA 報 HOUR_OF_DAY: 0 -> 1異常的解決過程和方案

myskies發表於2021-11-04

在進行資料查詢時,控制檯報了Caused by: com.mysql.cj.exceptions.WrongArgumentException: HOUR_OF_DAY: 0 -> 1異常,查詢得知:這是由於查mysql庫,轉換型別為datetime型別的欄位引起的。

網上的解決方案有多種,大多數都是通過設定時區來解決的,但遺憾的是通過測試我發現即使在將資料跑在時區正確的資料庫上,在執行起來我這仍然出錯。

最後發現我的問題出現在夏令時上。

夏令時

記得小時候過過幾個夏令時,大概的意思就是在某一天把表調快1個小時,然後再到某一天把表再調慢1個小時。這直接造成的問題的是:xxxx年xx月xx日會對應上兩個時間戳。
比如我們假設把表調慢的那一天是2021年10月4日的12點。具體的操作是當時鍾第一次經過2021年10月4日12點時,我們把表調到2021年10月4日11點。所以在2021年10月4日11點至12點,我們會重新過一次。

關於時間這塊,曾經回達到一個時間戳為負的問題,也有那麼點意思:https://segmentfault.com/q/1010000038248983,趕興趣的可以看看。

那麼問題來了,比我們記錄使用者的出生時間,精確到分鐘。如果這個人錄入的是2021年10月4日11點20分,那我們的系統沒有辦法來準確的判斷這個時間是第一個11點20分,還是過1小時後的第二個11點20分。

夏令時,還給我們帶來的另一個問題。有些時間是對應不上時間戳的。
再比如我們設定在2021年5月1日0時,將表調快1時,則在歷史上不會出現2021年5月1日0時至1時的時間,所以如果我們統計出生時間點,使用者寫的是:2021年5月1日0時30分,則該資料必須是個假資料。

排查

夏令時講完後,我們講下排查過程。其實並不是所有的資料在查詢時,都會報這種異常,所以要把那個特殊的點找出來,這裡給一種最笨的展示方法:

    boolean last = false;
    int page = 0;
    Pageable pageable = PageRequest.of(page, 1);
    while (!last) {
      try {
        Page<Resident> residents = this.residentRepository.findAll(specification, pageable);
        page++;
        pageable = PageRequest.of(page, 1);
        last = residents.isLast();
      } catch (Exception e) {
        last = true;
        e.printStackTrace();
        this.logger.info("當前頁" + pageable.getPageNumber());
      }
    }

最終控制檯列印資訊:2021-11-04 13:25:38.562 INFO 4226 --- [nio-8081-exec-7] c.y.s.service.ResidentServiceImpl : 當前頁1089

然後我們去資料表中把這條記錄查出來:

select * FROM resident limit 1089, 1

image.png

我們發現此人的出生日期是1947年4月15日0時0分0分。其實這個日期使用者僅僅是輸入了1947-4-15,只是我們存的時候自動新增了0時0分0秒。但恰巧,這個數字對應的時間戳,它恰恰就是一個無效數字。

測試程式碼如下:

  @Test
  void time() {
    Calendar calendar = Calendar.getInstance();
    // 啟用嚴格檢查模式
    calendar.setLenient(false);
    calendar.set(1947, 3, 15, 0, 0, 0);
    System.out.println(calendar.getTime());
  }

異常內容:java.lang.IllegalArgumentException: HOUR_OF_DAY: 0 -> 1
它是在說:你說自己是0點出生的,但是本JAVA大牛查了一下,1947年4月15日就沒有0點,當天的最小值是1點。

解決問題

問題找到了,解決便是最簡單的一環。

  1. 找到報錯的歷史資料,將0點改成8點。
  2. 找到歷史的程式碼,將0點改成8點。
  public static Timestamp getTimeStampFormIdNumber(String idNumber) {
    // 進行出生日期賦值
    int year = Integer.valueOf(idNumber.substring(6, 10));
    int month = Integer.valueOf(idNumber.substring(10, 12));
    int day = Integer.valueOf(idNumber.substring(12, 14));

    Calendar calendar = Calendar.getInstance();
-   calendar.set(year, month - 1, day, 0, 0, 0);
+   calendar.set(year, month - 1, day, 12, 0, 0);
    return new Timestamp(calendar.getTimeInMillis());
  }

至於為什麼改成12點,是由於我發現夏令時的修改(調快或調慢)都規避了12點,所以時間為12點,則可以有效避免無效時間戳的問題.當然了,你還可以改成13點,只要不是凌晨的那幾個小時,都沒有什麼問題。

相關文章