You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot JPA集成Hibernate查询PostgreSQL时LocalTime毫秒丢失问题

问题根因

你调试发现的逻辑就是核心根因:

  • Hibernate 6.0之前的版本中,LocalTime默认使用TimeTypeDescriptor处理参数绑定,该实现会强制将LocalTime转换为java.sql.Time对象,而java.sql.Time的设计本身不支持存储毫秒,会自动截断时间精度到秒级,因此实际传入数据库的查询参数已经丢失了毫秒部分,才会出现查询范围不准的问题。
  • 直接在psql中执行SQL可以得到正确结果,是因为你手动拼接的参数是带毫秒的字符串格式,PostgreSQL可直接解析到毫秒精度,没有经过JDBC预编译参数绑定的截断逻辑,所以结果正常。
推荐解决方案

按实现成本和适配性从高到低排序:

方案1:使用Hibernate官方原生高精度LocalTime映射(适配Hibernate 5.3+)

Hibernate 5.3开始已经提供了原生支持毫秒精度的LocalTimeType,该类型会直接使用JDBC4.2的setObject方法绑定LocalTime对象,不会转换为java.sql.Time,完全不会丢失毫秒精度。
只需要在实体类对应字段上补充注解即可:

@Column(columnDefinition = "time(3)") // 3指毫秒精度,和数据库字段定义对齐
@Type(type = "org.hibernate.type.LocalTimeType")
private LocalTime actionTime;

不需要修改任何查询逻辑即可生效。

方案2:自定义用户类型实现高精度绑定(适配所有Hibernate版本)

如果Hibernate版本低于5.3,可自行实现自定义Hibernate用户类型,直接将LocalTime参数传递给PostgreSQL JDBC驱动(PostgreSQL JDBC驱动42.0及以上版本原生支持LocalTime类型绑定,保留毫秒精度)。
示例代码如下:

public class PreciseLocalTimeType implements UserType {
    @Override
    public int[] sqlTypes() {
        return new int[]{Types.TIME};
    }

    @Override
    public Class returnedClass() {
        return LocalTime.class;
    }

    @Override
    public boolean equals(Object x, Object y) throws HibernateException {
        return Objects.equals(x, y);
    }

    @Override
    public int hashCode(Object x) throws HibernateException {
        return x == null ? 0 : x.hashCode();
    }

    @Override
    public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException {
        String timeStr = rs.getString(names[0]);
        return timeStr == null ? null : LocalTime.parse(timeStr);
    }

    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException {
        if (value == null) {
            st.setNull(index, Types.TIME);
            return;
        }
        // 直接绑定LocalTime对象,不转换为java.sql.Time
        st.setObject(index, (LocalTime) value);
    }

    // 省略其他默认实现方法,按Hibernate UserType规范补全即可
}

在实体字段上关联该自定义类型:

@Type(type = "com.yourpackage.PreciseLocalTimeType")
private LocalTime actionTime;

方案3:查询时转换为字符串参数(无需修改实体映射)

如果不想调整现有实体类的映射配置,可以在构造Specification查询时,将时间参数格式化为带毫秒的字符串,调用PostgreSQL内置函数转换为时间类型,避开Hibernate的参数截断逻辑:

// 时间格式化工具,固定输出3位毫秒
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");

// 构造查询谓词示例
LocalTime startTime = LocalTime.parse("09:00:00.203");
LocalTime endTime = LocalTime.parse("09:00:01.000");

predicate.and(
    criteriaBuilder.greaterThanOrEqualTo(
        root.get("actionTime"),
        criteriaBuilder.function(
            "to_time", 
            LocalTime.class, 
            criteriaBuilder.literal(startTime.format(TIME_FORMATTER))
        )
    ),
    criteriaBuilder.lessThanOrEqualTo(
        root.get("actionTime"),
        criteriaBuilder.function(
            "to_time", 
            LocalTime.class, 
            criteriaBuilder.literal(endTime.format(TIME_FORMATTER))
        )
    )
);

内容的提问来源于stack exchange,提问作者user3120928

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 10:06:04