Spring JPA:规范中的 Join 和 OrderBy

Spring JPA: Join and OrderBy in Specification

我在规范构建方面遇到了一些问题。 我有两个实体:
候选人:

@Entity
@Table(name = "candidates")
public class Candidate extends BaseEntity {

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "category_id", insertable = false, updatable = false)
    @JsonIgnore
    private Category category;

    @Column(name = "category_id")
    private Long categoryId;

    …
}

类别

@Entity
@Table(name = "categories")
public class Category extends BaseEntity {

    @OneToMany(mappedBy = "category")
    @JoinColumn(name = "category_id")
    @JsonBackReference(value = "categories-candidates")
    private List<Candidate> candidates = new ArrayList<>();
    …
}

有些 Candidatecategory 字段中有空值。我需要按类别的 name 字段排序,而不是按某些候选人的字段排序。同时,我有候选人字段 uploadId 的 where-condition。 我需要创建等价于 SQL 的规范(我检查了这个请求 - 这正是我需要的):

SELECT * FROM candidates 
LEFT JOIN categories ON candidates.category_id = categories.id 
WHERE candidates.upload_id = 1 
ORDER BY categories."name"

我试过这样做:

public static Specification<Candidate> candidatesByUploadId(final long uploadId) {
    return ((root, criteriaQuery, criteriaBuilder) -> {
        Join<Candidate, Category> join = root.join("category", JoinType.LEFT);
        criteriaQuery.orderBy(criteriaBuilder.desc(join.get("name")));
        return criteriaBuilder.equal(join.get("uploadId"), uploadId);
    });
}

但我不能:

我怎样才能得到这个?

您可以使用 Join.getOn()Join 转换为 Predicate

public static Specification<Candidate> candidatesByUploadId(final long uploadId) {
    return ((root, criteriaQuery, criteriaBuilder) -> {
        Join<Candidate, Category> join = root.join("category", JoinType.LEFT);
        criteriaQuery.orderBy(criteriaBuilder.desc(join.get("name")));
        return criteriaBuilder.equal(root.get("uploadId"), uploadId)
                              // here it is
                              .and(join.getOn());
    });
}

先到先得

您需要将一列指定为 updatable = false & insertable = false,因为您在 Candidate 实体中指定两个属性 (category & categoryId) .因此,您的 Candidate 实体将如下所示

@Entity
@Table(name = "candidates")
public class Candidate extends BaseEntity {

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "category_id", insertable = false, updatable = false)
    @JsonIgnore
    private Category category;

    @Column(name = "category_id", updatable = false, insertable = false)
    private Long categoryId;

    …
}

那么您的规格将如下所示:

public class CandidateSpecification {

    // Specification for upload id clause
    public static Specification<Candidate> candidatesByUploadId(final long uploadId) {
        if(uploadId == null) return null;
        return ((root, query, builder) -> {
            return builder.equal(root.get(Candidate_.uploadId), uploadId);
        });
    }
    
    // Specification for left join category.
    public static Specification<Candidate> joinCategory() {
    
        return (root, query, builder) -> {
            root.join(Candidate_.category, JoinType.LEFT);
            return builder.conjuction();
        }
    }
    ...

}

现在是查询部分

public List<Candidate> getCandidatesByUploadIdSortedByCategoryName(Long uploadId) {
    Specification<Candidate> specification = Specification.where(CandidateSpecification.candidatesByUploadId(uploadId))
    .and(CandidateSpecification.joinCategory);

    return candidateRepository.findAll(specification, Sort.by("category.name"));
}