在进行数据查询时,控制台报了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秒。但恰巧,这个数字对应的时间戳,它恰恰就是一个无效数字。
测试代码如下:
@Testvoid 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点。
解决问题
问题找到了,解决便是最简单的一环。
- 找到报错的历史数据,将0点改成8点。
- 找到历史的代码,将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将返回的字段重命名到另一个属性上,然后再
赋值到原字段中。