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<>();
…
}
有些 Candidate
在 category
字段中有空值。我需要按类别的 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 中没有“uploadId”字段,因为这是 Category-to-Candidate 的 join left join,不是 vica versa
- 我需要所有候选记录,即使类别为空,所以我不能使用内部联接
- 我无法使用
JoinType.RIGHT
- 它不受支持,我遇到了异常
- 我尝试更改实体中的关系所有者,但没有帮助
- 我不应该使用
@Query
东西,我需要把它变成 Specification<>
- 如果我用
root
代替 join
写 return criteriaBuilder.equal(root.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"));
}
我在规范构建方面遇到了一些问题。
我有两个实体:
候选人:
@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<>();
…
}
有些 Candidate
在 category
字段中有空值。我需要按类别的 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 中没有“uploadId”字段,因为这是 Category-to-Candidate 的 join left join,不是 vica versa
- 我需要所有候选记录,即使类别为空,所以我不能使用内部联接
- 我无法使用
JoinType.RIGHT
- 它不受支持,我遇到了异常 - 我尝试更改实体中的关系所有者,但没有帮助
- 我不应该使用
@Query
东西,我需要把它变成Specification<>
- 如果我用
root
代替join
写return criteriaBuilder.equal(root.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"));
}