JPA查询方法无返回结果 生成SQL在Postgres控制台执行正常
使用Spring Data JPA做数据查询时,调用Repository方法无结果返回,将JPA日志打印的生成SQL手动放到PostgreSQL控制台执行,可以正常得到正确结果。
相关代码与生成SQL如下:
- Repository层代码
public interface FireWeatherDay1CatRiskRepository extends JpaRepository<FireWeatherDay1CatRisk, Long> { List<FireWeatherDay1CatRisk> findByAdvTs(Instant advTs); }
- JPA日志打印的查询SQL
select fireweathe0_.id as id1_0_, fireweathe0_.adv_ts as adv_ts2_0_, fireweathe0_.category as category3_0_, fireweathe0_."end" as end4_0_, fireweathe0_.geom as geom5_0_, fireweathe0_.start as start6_0_ from fire_weather_day1_cat_risk fireweathe0_ where fireweathe0_.adv_ts='2021-09-28T17:00:00Z';
这类问题90%以上都是日志展示的SQL和真实执行的预编译语句参数不一致导致的:JPA(底层是Hibernate)打印的SQL是框架为了方便排查,手动把预编译参数拼接成字符串生成的日志,根本不是数据库真实收到的执行语句。真实查询走JDBC PreparedStatement参数绑定流程,和日志展示的内容可能存在明显偏差,按以下优先级排查即可:
第一优先级:确认真实绑定的参数值
不要直接参考日志里拼接好的SQL,直接开启JDBC参数绑定的TRACE级别日志,查看实际传给数据库的adv_ts参数真实值。Spring Boot项目加以下配置即可开启:logging: level: org.hibernate.type.descriptor.sql.BasicBinder: TRACE重点核对两个核心点:
- 时间精度是否匹配:如果数据库adv_ts字段是秒级精度(比如建表时设置为
timestamp(0)/timestamptz(0)),但代码传入的Instant带毫秒/纳秒值,就会匹配失败——日志拼接SQL时会自动截断小数秒,看起来和手动执行的SQL参数完全一致,实际传参带了精度后缀,自然查不到对应数据。 - 时间是否存在时区偏移:如果日志里打印的真实参数值和预期时间差了整数小时,就是时区配置问题。
- 时间精度是否匹配:如果数据库adv_ts字段是秒级精度(比如建表时设置为
第二优先级:修复精度不匹配问题
如果确认是精度偏差,直接把传入查询的Instant截断到和数据库字段一致的精度即可,示例代码:import java.time.temporal.ChronoUnit; // 数据库存储为秒级时间戳时,将入参截断到秒 Instant queryParam = advTs.truncatedTo(ChronoUnit.SECONDS); List<FireWeatherDay1CatRisk> result = repository.findByAdvTs(queryParam);也可以调整数据库字段精度,将adv_ts字段修改为
timestamptz(6)支持微秒级存储,和Java侧Instant的默认精度对齐,一劳永逸解决精度匹配问题。第三优先级:修复时区配置不匹配问题
Instant本身是UTC时区的瞬时值,如果JDBC连接未指定时区,驱动会默认取JVM时区做时间转换,一旦JVM时区、数据库服务端时区、业务预期时区不统一,Instant转数据库时间类型时就会出现小时级偏移。直接在JDBC连接串中强制指定UTC时区即可解决:# JDBC连接串示例 spring.datasource.url=jdbc:postgresql://数据库地址:5432/库名?TimeZone=UTC同时统一JVM启动参数、PostgreSQL服务端的timezone配置,避免多层隐式时区转换。
第四优先级:检查实体类映射配置
JPA 2.2及以上版本原生支持Instant类型的时间映射,不需要在实体类的advTs字段上加@Temporal注解,多余的@Temporal注解反而会导致类型转换异常,存在的话直接删除即可。如果使用的JPA版本低于2.2,要么升级Hibernate/Spring Data JPA到稳定版本,要么自定义属性转换器完成Instant和数据库时间类型的映射。
极低概率场景:以上配置全部校验正常仍存在问题时,检查调用Repository方法的事务配置,如果事务隔离级别设置为
SERIALIZABLE或者只读事务配置异常,可能导致数据可见性问题,可以将查询方法放到无事务的独立方法中验证。
内容的提问来源于stack exchange,提问作者user3282275

