'Building a select query using Spring Data Specification
I'm trying to build a select query with Spring Data Specification. The query in question is the following:
SELECT * FROM product WHERE id IN (SELECT product_id FROM product_tags WHERE tags IN ('GRADUATION', 'BIRTHDAY'));
The user is supposed to provide a set of tags to be matched with the IN operator in the subquery, BIRTHDAY and GRADUATION are some examples. I've tried building my solution off this answer but ran into some trouble.
public static Specification<Product> withTags(Set<Tags> tags) {
return tags == null ?
null :
(root, query, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
Subquery<Tags> subquery = query.subquery(Tags.class);
Root<Tags> subqueryRoot = subquery.from(Tags.class);
subquery.select(subqueryRoot.get("product_tags").get("product_id"));
subquery.where(criteriaBuilder.trim(subqueryRoot.get("product").get("id")).in(tags));
predicates.add(subqueryRoot.get("*").in(subquery));
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};
}
The problem here is that I'm trying to create a subquery from Tags which is not registered as an entity but it is rather an enum. Thus, executing the code gives me an error (This is the only error I've encountered so far, please point out parts of the code that may potentially cause other errors).
public enum Tags {
BIRTHDAY("birthday"),
GRADUATION("graduation"),
GET_WELL_SOON("get well soon"),
RIBBON("ribbon"),
WRAPPING_PAPER("wrapping paper");
final String tagName;
private Tags(String tagName) {
this.tagName = tagName;
}
public String getTagName() {
return tagName;
}
}
Not sure if this will help, but in the Product class there is a field tags denoted with @ElementCollection. Spring automatically creates a table named 'product_tags' with this, and the subquery selects from this table.
@ElementCollection(fetch = FetchType.EAGER)
@Enumerated(EnumType.STRING)
private Set<Tags> tags;
If possible, I would like to translate this query instead of the first one
SELECT * FROM product WHERE id IN (SELECT product_id FROM product_tags WHERE tags = ANY(ARRAY['GRADUATION', 'GET_WELL_SOON']));
UPDATE
I have edited my code
public static Specification<Product> withTags(Set<Tags> tags) {
return tags == null ?
null :
(root, query, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
Subquery<Long> subquery = query.subquery(Long.class);
Root<Product> subroot = subquery.from(Product.class);
subquery.select(subroot.get("id").get("tags"));
subquery.where(criteriaBuilder.trim(subroot.join("tags").get("id")).in(tags));
predicates.add(root.get("id").in(subquery));
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};
}
But now I'm getting this error
java.lang.IllegalStateException: Illegal attempt to dereference path source [null.id] of basic type
For reference, my tables are defined as such
product:
Column | Type | Collation | Nullable | Default
-------------+------------------------+-----------+----------+---------
id | bigint | | not null |
category | character varying(255) | | |
date_added | date | | |
description | character varying(255) | | |
name | character varying(255) | | |
price | double precision | | not null |
product_tags:
Column | Type | Collation | Nullable | Default
------------+------------------------+-----------+----------+---------
product_id | bigint | | not null |
tags | character varying(255) | | |
Solution 1:[1]
public static Specification<Product> withTags(Set<Tags> tags) {
return tags.isEmpty() ?
null:
(root, query, criteriaBuilder) -> {
Subquery<Tags> subquery = query.subquery(Tags.class);
Root<Product> subroot = subquery.from(Product.class);
subquery.select(subroot.get("id")).where(subroot.join("tags").in(tags));
Predicate predicate = root.get("id").in(subquery);
return criteriaBuilder.and(predicate);
};
I seemed to have found an answer. Tags.class works apparently, and from there I just had to tweak my query to be a join select. Not what I initially hoped to accomplish, but it works.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | CM K |
