Spring Boot原生查询如何将Java Date从服务器时区转换为用户时区查询数据
问题根因
- 你在pgAdmin中执行查询能得到正确结果,是因为
between的两个参数是Asia/Calcutta时区下的本地时间字符串,和左边rr.created_date at time zone 'utc' at time zone 'Asia/Calcutta'的输出类型(不带时区的Asia/Calcutta本地时间)完全匹配,比较逻辑符合预期。 - 代码中无论怎么设置Calendar的时区,最终调用
getTime()得到的Date对象本身不携带时区信息,仅代表1970年1月1日以来的毫秒数。当你把Date对象作为参数传给JDBC时,驱动会默认按数据库会话的时区把这个毫秒数转换为不带时区的timestamp,和左边转换后的Asia/Calcutta本地时间比较自然会出现偏差,这也是只有用户时区早上5:30之后查询结果才正确的原因。
解决方案
推荐两种可直接落地的修改方式,优先选第一种,性能更好:
方案1:修改SQL(推荐,可走created_date索引)
直接在SQL层完成时区转换,避免对表字段做函数转换,性能更高,同时不需要修改Java代码的参数生成逻辑:
SELECT * FROM records rr WHERE rr.created_date >= ?1::DATE AT TIME ZONE 'Asia/Calcutta' AT TIME ZONE 'utc' AND rr.created_date < (?1::DATE + INTERVAL '1 day') AT TIME ZONE 'Asia/Calcutta' AT TIME ZONE 'utc'
参数?1直接传用户请求的日期字符串即可,比如2021-10-16,不需要处理时分秒。
方案2:不改SQL,修改参数传递逻辑
沿用你现有的SQL,把参数从Date类型改为Asia/Calcutta本地时间的字符串,和你在pgAdmin中执行的逻辑完全一致:
- 首先生成Asia/Calcutta时区下的起止时间字符串,推荐用Java 8+的
java.time包(比Calendar更不容易出错):
import java.time.LocalDate; import java.time.ZoneId; // 用户请求的目标日期,比如2021-10-16 LocalDate targetDate = LocalDate.of(2021, 10, 16); ZoneId userZone = ZoneId.of("Asia/Calcutta"); // 生成起止时间字符串 String startParam = targetDate.atStartOfDay().toString(); String endParam = targetDate.plusDays(1).atStartOfDay().toString();
- 把
startParam和endParam作为字符串参数传入你的原SQL,同时调整SQL的边界判断更稳妥:
SELECT * FROM records rr WHERE rr.created_date AT TIME ZONE 'utc' AT TIME ZONE 'Asia/Calcutta' >= ?1 AND rr.created_date AT TIME ZONE 'utc' AT TIME ZONE 'Asia/Calcutta' < ?2
内容的提问来源于stack exchange,提问作者Sachinthana Aluvihare
相关产品推荐
相关产品推荐

