Spring Data JPA 报 HOUR_OF_DAY: 0 -> 1异常的解决过程和方案

在进行数据查询时,控制台报了Caused by: com.mysql.cj.exceptions.WrongArgumentException: HOUR_OF_DAY: 0 -> 1异常,查询得知:这是由于查mysql库,转换类型为datetime类型的字段引起的。

网上的解决方案有多种,大多数都是通过设置时区来解决的,但遗憾的是通过测试我发现即使在将数据跑在时区正确的数据库上,在执行起来我这仍然出错。

最后发现我的问题出现在夏令时上。

夏令时

记得小时候过过几个夏令时,大概的意思就是在某一天把表调快1个小时,然后再到某一天把表再调慢1个小时。这直接造成的问题的是:xxxx年xx月xx日会对应上两个时间戳。
比如我们假设把表调慢的那一天是2022年10月4日的12点。具体的操作是当时钟第一次经过2022年10月4日12点时,我们把表调到2022年10月4日11点。所以在2022年10月4日11点至12点,我们会重新过一次。

那么问题来了,比我们记录用户的出生时间,精确到分钟。如果这个人录入的是2022年10月4日11点20分,那我们的系统没有办法来准确的判断这个时间是第一个11点20分,还是过1小时后的第二个11点20分。

夏令时,还给我们带来的另一个问题。有些时间是对应不上时间戳的。
再比如我们设置在2023年5月1日0时,将表调快1时,则在历史上不会出现2023年5月1日0时至1时的时间,所以如果我们统计出生时间点,用户写的是:2023年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());
      }
    }

最终控制台打印信息:2023-06-26 09:25:38.562 INFO 4226 --- [nio-8081-exec-7] c.y.s.service.ResidentServiceImpl : 当前页1089

然后我们去数据表中把这条记录查出来:

select * FROM resident limit 1089, 1

我们发现此人的出生日期是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点,只要不是凌晨的那几个小时,都没有什么问题。

3.修改服务器中的jdk版本(没有参考,自己搜搜哈,把服务器和本地的改一致就行了)

4.修改mysql中的配置和jdbc连接时配置(jdbc连接后面加上&serverTimezone=GMT%2B8,不要使用&serverTimezone=Asia/Shanghai)

5.修改代码,将代码中java.util.Date包改为java.sql.Date,其它调用该属性的,转换一下参数。

6.将Date类型更改成String,然后覆盖getter方法,返回值依旧是Date类型

结论

遇到异常的date值可以使用String去接收,然后手动加一个Date的getter方法。

如果不想改动该字段值的类型则可以考虑自己写sql将返回的字段重命名到另一个属性上,然后再

赋值到原字段中。

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值