I have some problem with Specification construction.
I have two entities:
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")
private Long categoryId;
…
}
and Category:
@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<>();
…
}
Some Candidate
has null in category
field. And I need to sort by Category's name
field, not by some Candidate's fields. At the same time, I have where-condition by Candidate's field uploadId
.
I need to create Specification to equivalense of SQL (I checked this request - this is exactly what I need):
SELECT * FROM candidates
LEFT JOIN categories ON candidates.category_id = categories.id
WHERE candidates.upload_id = 1
ORDER BY categories."name"
I tried to do that:
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);
});
}
But I can't:
JoinType.RIGHT
- it doesn't supported and I got the Exception@Query
things, I need to do it into Specification<>
return criteriaBuilder.equal(root.get("uploadId"), uploadId);
with root
instead join
, I haven't join resultsHow I can get this?
First thing is first
You need to specify one column as updatable = false
& insertable = false
, as you are specifying two properties in Candidate
entity (category
& categoryId
). So your Candidate
entity will look like following
@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;
…
}
Then your specification will look like following:
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();
}
}
...
}
Now the query part
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"));
}