如何通过分页在 Spring Data JPA 中的 SELECT 子句中按别名对投影进行排序?

Posted

技术标签:

【中文标题】如何通过分页在 Spring Data JPA 中的 SELECT 子句中按别名对投影进行排序?【英文标题】:How to sort projection by alias from SELECT clause in Spring Data JPA with pagination? 【发布时间】:2017-10-15 02:12:43 【问题描述】:

我创建了这两个实体来演示我的问题:

OwnerEntity.java:

@Entity
public class OwnerEntity 

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

    @Size(min = 1)
    @OneToMany(mappedBy = "ownerEntity", cascade = CascadeType.ALL)
    private Set<ChildEntity> childEntities = new HashSet<>();

ChildEntity.java:

@Entity
public class ChildEntity 

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

    private String name;

    @NotNull
    @ManyToOne(optional = false)
    private OwnerEntity ownerEntity;

    public ChildEntity() 
    

    public Long getId() 
        return id;
    

    public String getName() 
        return name;
    

    public void setName(String name) 
        this.name = name;
    

    public OwnerEntity getOwnerEntity() 
        return ownerEntity;
    

    public void setOwnerEntity(OwnerEntity ownerEntity) 
        this.ownerEntity = ownerEntity;
    

我想编写一个查询来显示来自 OwnerEntity 和一个加入的 ChildEntity 的信息。我创建了一个投影:

OwnerEntityProjection.java:

public interface OwnerEntityProjection 

    Long getId();

    String getName();


我的 JpaRepository:

public interface OwnerEntityRepository extends JpaRepository<OwnerEntity, Long> 

    @Query(" SELECT                                        " +
           "       ownerEntity.id AS id,                   " +
           "       childEntities.name AS name              " +
           " FROM OwnerEntity ownerEntity                  " +
           " JOIN ownerEntity.childEntities childEntities  ")
    // There must be also WHERE clause, but for demonstration it is omitted
    Slice<OwnerEntityProjection> findAllPaginated(Pageable pageRequest);


现在当我运行这个简单的测试时:

@Test
public void findAllPaginatedTest() 
    Pageable pageRequest = new PageRequest(0, 3, Sort.Direction.ASC, "name");
    Slice<OwnerEntityProjection> OwnerEntityProjectionsPaginated =
            ownerEntityRepository.findAllPaginated(pageRequest);

我收到以下错误:

org.hibernate.QueryException: could not resolve property: name of: com.example.domain.OwnerEntity

我还在日志中检查了生成的 JQPL:

... order by ownerEntity.name asc

如您所见,Spring Data Jpa 在 FROM 子句中附加了第一个实体别名。我发现如果我将 PageRequest 更改为:

new PageRequest(0, 3, Sort.Direction.ASC, "childEntities.name");

它可以正常工作,但我不想将排序属性传递到具有 JPQL 查询中某处的别名的存储库。我想传递存储库方法返回的投影中直接存在的属性。如果我像这样在 JPQL 查询中手动编写 ORDER BY:

... ORDER BY name ASC

然后这个查询也运行没有任何错误,因为我可以从 ORDER BY 子句中引用 SELECT 子句中的别名。 有没有办法告诉 Spring Data Jpa 在不附加 FROM 或 JOIN 子句的别名的情况下执行排序?像这样的:

new PageRequest(0, 3, Sort.Direction.ASC, "name") ===> ORDER BY name asc

【问题讨论】:

你能解决这个问题吗?我也面临同样的问题 【参考方案1】:

这是 Spring Data 中的一个错误:未正确检测到别名。也被举报了here。

applySorting 方法中的 QueryUtils 中,仅检测到(外部)连接别名和函数别名恰好具有一对括号。属性的简单别名目前不起作用。

一种解决方法是在构建PageRequest 时使用带有括号中的别名的JpaSort.unsafe,例如

PageRequest.of(0, 3, JpaSort.unsafe(Sort.Direction.ASC, "(name)"))

顾名思义,这是不安全的,在根据用户输入动态排序时,因此只能用于硬编码排序。

【讨论】:

感谢您的解决方案。它拯救了我的一天 非常感谢! 这是一个很棒的信息,你救了我【参考方案2】:

在 Pageable 控制器入口点的上下文中,我将 Dario Seidl 的答案改编为以下内容:

public static Pageable parenthesisEncapsulation(final Pageable pageable) 

    Sort sort = Sort.by(Collections.emptyList());
    for (final Sort.Order order : pageable.getSort()) 
        if (order.getProperty().matches("^\\(.*\\)$")) 
            sort = sort.and(JpaSort.unsafe(order.getDirection(), order.getProperty()));
         else 
            sort = sort.and(Sort.by(order.getDirection(), order.getProperty()));
        
    
    return PageRequest
        .of(pageable.getPageNumber(), pageable.getPageSize(), sort);

我可以像这样给我的控制器排序指令:

/myroute?sort=(unsafesortalias),desc&sort=safesortfield,asc

【讨论】:

【参考方案3】:
    String orderSort=page.getSort().toString();                
    Pageable page1 = 
    PageRequest.of(page.getPageNumber(),page.getPageSize(),
    JpaSort.unsafe(Sort.Direction.valueOf
    (orderSort.substring(page.getSort().toString().indexOf(':')+2,
    orderSort.length())),
    orderSort.substring(0,page.getSort().toString().indexOf(')')+1)));

【讨论】:

虽然此代码可能会解决问题,including an explanation 关于如何以及为什么解决问题将真正有助于提高您的帖子质量,并可能导致更多的赞成票。请记住,您正在为将来的读者回答问题,而不仅仅是现在提问的人。请edit您的回答添加解释并说明适用的限制和假设。

以上是关于如何通过分页在 Spring Data JPA 中的 SELECT 子句中按别名对投影进行排序?的主要内容,如果未能解决你的问题,请参考以下文章

Spring Data Jpa: 分页和排序

如何在 Spring Data JPA 中使用带有分页的投影接口?

如何使用带有 Pageable 的 Spring Data JPA 分页来更改数据?

Spring Data 系列学习Spring Data JPA 自定义查询,分页,排序,条件查询

在Spring Boot中使用Spring-data-jpa实现分页查询(转)

Spring Data JPA:使用连接表进行排序和分页