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

2022-09-03 01:33:13

我创建了这两个实体来证明我的问题:

所有者实体.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<>();
}

子实体.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;
    }
}

我想编写一个查询,该查询将显示来自所有者实体和一个已加入子实体的信息。我创建了一个投影:

所有者实体项目.java:

public interface OwnerEntityProjection {

    Long getId();

    String getName();

}

My 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中的一个错误:未正确检测到别名。这里也有报道。

方法的 QueryUtils 中,仅检测到(外部)联接别名和函数别名,并且只包含一对括号。属性的简单别名当前不起作用。applySorting

一种解决方法是在构建 时与括号中的别名一起使用,例如JpaSort.unsafePageRequest

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

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


答案 2

在可分页控制器的入口点的上下文中,我将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


推荐