使用弹簧数据规范构建动态选择查询
我正在尝试使用 Spring Data 规范构建一个选择查询。有问题的查询如下:
SELECT * FROM Product WHERE id IN (SELECT Product_id FROM Product_tags WHERE Tags IN ('GRADUATION', 'BIRTHDAY'));
用户应该提供一组与子查询中的 IN
运算符匹配的标签,例如 BIRTHDAY 和 GRADUATION。我尝试根据这个答案构建我的解决方案,但遇到了一些麻烦。
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]));
};
}
这里的问题是我试图从 Tags
创建一个子查询,它没有注册为实体,而是一个枚举。因此,执行代码会出现错误(这是迄今为止我遇到的唯一错误,请指出代码中可能导致其他错误的部分)。
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;
}
}
不确定这是否有帮助,但在 Product
类中,有一个用 @ElementCollection
表示的字段 tags
。 Spring 自动创建一个名为“product_tags”的表,子查询从此表中进行选择。
@ElementCollection(fetch = FetchType.EAGER)
@Enumerated(EnumType.STRING)
private Set<Tags> tags;
如果可能,我想翻译此查询而不是第一个查询
SELECT * FROM Product WHERE id IN (SELECT Product_id FROM Product_tags WHERE Tags = ANY(ARRAY['GRADUATION', 'GET_WELL_SOON']));
更新
我已经编辑了我的代码,
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]));
};
}
但现在我收到此错误
java.lang.IllegalStateException: Illegal attempt to dereference path source [null.id] of basic type
作为参考,我的表定义为这样的
产品:
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) | | |
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) | | |
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我似乎找到了答案。
Tags.class
显然可以工作,从那里我只需将查询调整为连接选择。这不是我最初希望实现的目标,但它确实有效。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.