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

Spring JPA:PostgreSQL JSONB字段分页排序失效问题

问题:JSONB嵌套属性排序失败(Criteria API + PageRequest)

使用Criteria API Specification结合PageRequest查询记录时,按实体直接属性(如division)排序正常,但按JSONB嵌套属性publicationSchedule.deadlineDate排序时抛出异常。

实体类定义

public class Publication {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "book_price")
    private String bookPrice;

    @Column(name = "division")
    private String division;

    @Column(columnDefinition = "JSONB", name = "publication_schedule")
    @Type(JsonBinaryType.class)
    PublicationSchedule publicationSchedule;
}

// PublicationSchedule为JSON嵌套对象,非实体类
public class PublicationSchedule{

    @JsonSerialize(using = TimeStampSerializer.class)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date deadlineDate;

    @JsonSerialize(using = TimeStampSerializer.class)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date publicationDate;
}

异常场景

当排序字段设置为division时查询正常;设置为publicationSchedule.deadlineDate时,执行以下代码抛出异常:

Specification<Publication> specification = Specification.where(null);
// <<Specification代码生成逻辑>>
Page<Publication> searchResults = publicationRepository.findAll(specification, pageRequest);

异常信息

ERROR org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet].log(175) - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.dao.InvalidDataAccessResourceUsageException: Attribute 'com.elsevier.rpc.service.bff.entity.Publication#publicationSchedule(BASIC)' is not joinable] with root cause
org.hibernate.query.SemanticException: Attribute 'com.elsevier.rpc.service.bff.entity.Publication#publicationSchedule(BASIC)' is not joinable
    at org.hibernate.query.sqm.tree.domain.AbstractSqmFrom.buildSingularJoin(AbstractSqmFrom.java:762) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final]
    at org.hibernate.query.sqm.tree.domain.AbstractSqmFrom.buildJoin(AbstractSqmFrom.java:697) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final]
    at org.hibernate.query.sqm.tree.domain.AbstractSqmFrom.join(AbstractSqmFrom.java:410) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final]
    at org.hibernate.query.sqm.tree.domain.AbstractSqmFrom.join(AbstractSqmFrom.java:69) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final]
    at org.springframework.data.jpa.repository.query.QueryUtils.getOrCreateJoin(QueryUtils.java:903) ~[spring-data-jpa-3.2.4.jar:3.2.4]
    at org.springframework.data.jpa.repository.query.QueryUtils.toExpressionRecursively(QueryUtils.java:799) ~[spring-data-jpa-3.2.4.jar:3.2.4]
    at org.springframework.data.jpa.repository.query.QueryUtils.toExpressionRecursively(QueryUtils.java:769) ~[spring-data-jpa-3.2.4.jar:3.2.4]
    at org.springframework.data.jpa.repository.query.QueryUtils.toExpressionRecursively(QueryUtils.java:765) ~[spring-data-jpa-3.2.4.jar:3.2.4]
    at org.springframework.data.jpa.repository.query.QueryUtils.toJpaOrder(QueryUtils.java:754) ~[spring-data-jpa-3.2.4.jar:3.2.4]
    at org.springframework.data.jpa.repository.query.QueryUtils.toOrders(QueryUtils.java:706) ~[spring-data-jpa-3.2.4.jar:3.2.4]
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.getQuery(SimpleJpaRepository.java:753) ~[spring-data-jpa-3.2.4.jar:3.2.4]
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.getQuery(SimpleJpaRepository.java:710) ~[spring-data-jpa-3.2.4.jar:3.2.4]
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll(SimpleJpaRepository.java:447) ~[spring-data-jpa-3.2.4.jar:3.2.4]

问题根源

Spring Data JPA的QueryUtils会尝试对嵌套属性路径(如publicationSchedule.deadlineDate)执行JOIN操作,但publicationSchedule是JSONB类型的基本属性(BASIC),并非关联实体,无法进行JOIN,因此触发异常。

解决方案

方案1:使用JpaSort.unsafe直接指定JSONB排序表达式

直接通过数据库的JSONB语法构建排序规则,绕过默认的属性路径解析:

// 构建JSONB字段的排序规则(PostgreSQL语法)
// 若需按日期类型排序,需将JSON字符串转为日期
Sort sort = JpaSort.unsafe(Sort.Direction.DESC, 
    "to_timestamp(publication_schedule->>'deadlineDate', 'yyyy-MM-dd HH:mm:ss')");

// 构建带自定义排序的PageRequest
PageRequest pageRequest = PageRequest.of(pageNum, pageSize, sort);

// 执行查询
Page<Publication> searchResults = publicationRepository.findAll(specification, pageRequest);

方案2:在Specification中手动添加排序逻辑

通过CriteriaBuilder调用数据库函数提取JSON嵌套属性,手动构建排序条件:

Specification<Publication> specification = (root, query, cb) -> {
    // 提取JSONB中的deadlineDate并转为日期类型,添加排序
    Expression<Date> deadlineDateExpr = cb.function(
        "to_timestamp", 
        Date.class, 
        cb.function("jsonb_extract_path_text", String.class, root.get("publicationSchedule"), cb.literal("deadlineDate")),
        cb.literal("yyyy-MM-dd HH:mm:ss")
    );
    query.orderBy(cb.desc(deadlineDateExpr));
    
    // 原有查询条件逻辑
    return null;
};

// 此处PageRequest无需携带排序
PageRequest pageRequest = PageRequest.of(pageNum, pageSize);
Page<Publication> searchResults = publicationRepository.findAll(specification, pageRequest);

说明

  • 两种方案均针对PostgreSQL的JSONB语法,若使用其他支持JSON的数据库,需调整对应的函数或路径表达式。
  • 方案1更简洁,适合快速实现;方案2更符合Criteria API的面向对象风格,便于扩展复杂查询逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 09:12:07