JPQL使用Calendar查询日期:单日数据提取失败问题排查
我来帮你捋捋为啥你的查询返回0条数据,主要是两个坑:时区不匹配加上查询条件的逻辑小错误,咱们一步步解决。
首先,时区对应关系明显有问题
你贴的Calendar对象里,2018-04-13T00:00:00.000Z(UTC零点)居然对应Fri Apr 13 23:59:59 CEST,这完全不对啊!正常UTC零点对应CEST的凌晨2点(因为CEST比UTC早2小时)。这说明你的Calendar初始化或者时区转换逻辑出问题了,传入查询的startDate和endDate实际范围根本不是你想查的那天,这肯定查不到数据。
然后,查询条件的逻辑错了
你现在用的是FUNCTION('TRUNC', u.requestDate) > :startDate and FUNCTION('TRUNC', u.requestDate) < :endDate:
- 要是你想查4月13日的数据,
> :startDate会直接把所有TRUNC后等于13日的数据排除掉(毕竟13日不大于13日); - 而
< :endDate如果是15日的话,反而会包含14日的数据,完全跑偏了。
给你两个修正后的查询方案:
方案1:直接匹配截断后的日期
如果就想用TRUNC函数,改成等于目标日期就行:
select count(u) from EntityPoll u where pollId = :pollId and FUNCTION('TRUNC', u.requestDate) = :targetDate
这里的:targetDate就是你要查的那天的日期(注意时区要和数据库处理后的一致)。
方案2:用时间范围查询(更推荐,性能更好)
TRUNC函数可能会导致数据库没法用requestDate上的索引,换成时间范围查询更高效:
select count(u) from EntityPoll u where pollId = :pollId and u.requestDate >= :startOfDay and u.requestDate < :startOfNextDay
比如你要查CEST的4月13日,对应的UTC时间范围是2018-04-12T22:00:00.000Z(13日CEST零点的UTC时间)到2018-04-13T22:00:00.000Z(14日CEST零点的UTC时间),把这两个时间作为参数传进去就行。
最后,把时区问题彻底搞定
推荐统一用UTC处理时间,避免时区混乱:
- 数据库里的
requestDate统一存UTC时间; - 把你要查的CEST日期转换成对应的UTC时间范围,比如上面说的13日CEST对应UTC的12日22点到13日22点;
- 用方案2的范围查询,既准确又能利用索引。
要是你必须在数据库侧处理时区(比如Oracle),可以这么写HQL,先转时区再截断:
select count(u) from EntityPoll u where pollId = :pollId and FUNCTION('TRUNC', FUNCTION('FROM_TZ', u.requestDate, 'UTC') AT TIME ZONE 'CET') = :targetCestDate
这里把数据库里的UTC时间转成CET/CEST时区,再截断到日期,和你传入的CEST目标日期匹配。
另外,记得修复你的Calendar初始化代码,确保时区转换正确:
// 获取CEST时区的Calendar,设置为2018年4月13日零点 Calendar cestStart = Calendar.getInstance(TimeZone.getTimeZone("CET")); cestStart.set(2018, Calendar.APRIL, 13, 0, 0, 0); cestStart.set(Calendar.MILLISECOND, 0); // 转成UTC时间用于查询 Calendar utcStart = Calendar.getInstance(TimeZone.getTimeZone("UTC")); utcStart.setTimeInMillis(cestStart.getTimeInMillis());
内容的提问来源于stack exchange,提问作者Genaut

