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
相关产品推荐
相关产品推荐

