如何将 HashMap 传递给 CriteriaBuilder 并添加到 Predicates 列表
How to pass a HashMap to CriteriaBuilder and add to Predicates list
我目前在 API 请求正文中收到 Map<String, String>
。我有一个实体,
class TagEntity {
@Column(name = "name", length = 128, nullable = false)
private String name;
@Column(name = "value", length = 256, nullable = false)
private String value;
}
有一个 MachineEntity
有一个 Set<TagEntity>
。这是一个 @OneToMany
映射。我正在尝试根据 HashMap
(请求中)中传递的 name
和 value
获取机器实体,它们对应于 TagEntity
.
我试过下面这段代码。但是,只有当我在请求中传递一个名称-值对时它才有效。假设,如果 HashMap
包含 2 个元素,查询 return 是一个空列表,而它应该 return 2 个机器实体。
SetJoin<MachineEntity, TagEntity> join = root.join(MachineEntity_.tagEntities);
for (Map.Entry element : request.getTags().entrySet()) {
predicates.add(criteriaBuilder.and(
criteriaBuilder.equal(join.get(TagEntity_.name), element.getKey()),
criteriaBuilder.equal(join.get(TagEntity_.value), element.getValue())
));
}
有没有一种方法可以在 CriteriaBuilder
中设置 HashMap
而无需遍历 Map
?我不知道可以做些什么来解决这个问题。非常感谢您的帮助。
一如既往,将语句写在(大致)SQL 中有助于:
SELECT ...
FROM Machine m
WHERE -- repeat for each map entry
EXISTS (SELECT ... FROM Tag t WHERE t.machine_id = m.id AND t.name = ?1 AND t.value = ?2)
AND EXISTS (SELECT ... FROM Tag t WHERE t.machine_id = m.id AND t.name = ?3 AND t.value = ?4)
...
如果这符合您的需要,它可以转换为标准 API 大致如下(您可能需要对其进行调整):
private Subquery<TagEntity> makeExistsSubquery(CriteriaQuery<MachineEntity> q, CriteriaBuilder cb, Map.Entry<String,String> e) {
Subquery<TagEntity> subquery = q.subquery(TagEntity.class);
Root<TagEntity> tagRoot = subquery.from(TagEntity.class);
subquery.where(cb.and(
cb.equal(tagRoot.get(TagEntity_.name), e.getKey()),
cb.equal(tagRoot.get(TagEntity_.value), e.getValue())
));
return subquery;
}
private void your_method() {
// assuming something like this exists:
CriteriaQuery<MachineEntity> query = criteriaBuilder.createQuery(MachineEntity.class);
// do this:
for (Map.Entry element : request.getTags().entrySet()) {
predicates.add(criteriaBuilder.exists(makeExistsSubquery(query, criteriaBuilder, element)));
}
...
}
我不确定这个查询的效率如何。但我希望这能提示解决方案。
此外,我假设您想要匹配 ALL 给定标签的机器。如果您希望机器匹配给定标签的 ANY,SQL 查询会更简单:
SELECT ...
FROM Machine m JOIN Tag t ON t.machine_id = m.id
WHERE -- repeat for each map entry
t.name = ?1 AND t.value = ?2
OR t.name = ?3 AND t.value = ?4
...
Predicate tagsPredicate = criteriaBuilder.or(
request.getTags().entrySet().stream()
.map(e -> criteriaBuilder.and(
criteriaBuilder.equal(join.get(TagEntity_.name), e.getKey()),
criteriaBuilder.equal(join.get(TagEntity_.value), e.getValue())
))
.collect(Collectors.toList())
.toArray(new Predicate[0])
);
// add the tagsPredicate to your where clause, if the user has actually defined tag criteria
我目前在 API 请求正文中收到 Map<String, String>
。我有一个实体,
class TagEntity {
@Column(name = "name", length = 128, nullable = false)
private String name;
@Column(name = "value", length = 256, nullable = false)
private String value;
}
有一个 MachineEntity
有一个 Set<TagEntity>
。这是一个 @OneToMany
映射。我正在尝试根据 HashMap
(请求中)中传递的 name
和 value
获取机器实体,它们对应于 TagEntity
.
我试过下面这段代码。但是,只有当我在请求中传递一个名称-值对时它才有效。假设,如果 HashMap
包含 2 个元素,查询 return 是一个空列表,而它应该 return 2 个机器实体。
SetJoin<MachineEntity, TagEntity> join = root.join(MachineEntity_.tagEntities);
for (Map.Entry element : request.getTags().entrySet()) {
predicates.add(criteriaBuilder.and(
criteriaBuilder.equal(join.get(TagEntity_.name), element.getKey()),
criteriaBuilder.equal(join.get(TagEntity_.value), element.getValue())
));
}
有没有一种方法可以在 CriteriaBuilder
中设置 HashMap
而无需遍历 Map
?我不知道可以做些什么来解决这个问题。非常感谢您的帮助。
一如既往,将语句写在(大致)SQL 中有助于:
SELECT ...
FROM Machine m
WHERE -- repeat for each map entry
EXISTS (SELECT ... FROM Tag t WHERE t.machine_id = m.id AND t.name = ?1 AND t.value = ?2)
AND EXISTS (SELECT ... FROM Tag t WHERE t.machine_id = m.id AND t.name = ?3 AND t.value = ?4)
...
如果这符合您的需要,它可以转换为标准 API 大致如下(您可能需要对其进行调整):
private Subquery<TagEntity> makeExistsSubquery(CriteriaQuery<MachineEntity> q, CriteriaBuilder cb, Map.Entry<String,String> e) {
Subquery<TagEntity> subquery = q.subquery(TagEntity.class);
Root<TagEntity> tagRoot = subquery.from(TagEntity.class);
subquery.where(cb.and(
cb.equal(tagRoot.get(TagEntity_.name), e.getKey()),
cb.equal(tagRoot.get(TagEntity_.value), e.getValue())
));
return subquery;
}
private void your_method() {
// assuming something like this exists:
CriteriaQuery<MachineEntity> query = criteriaBuilder.createQuery(MachineEntity.class);
// do this:
for (Map.Entry element : request.getTags().entrySet()) {
predicates.add(criteriaBuilder.exists(makeExistsSubquery(query, criteriaBuilder, element)));
}
...
}
我不确定这个查询的效率如何。但我希望这能提示解决方案。
此外,我假设您想要匹配 ALL 给定标签的机器。如果您希望机器匹配给定标签的 ANY,SQL 查询会更简单:
SELECT ...
FROM Machine m JOIN Tag t ON t.machine_id = m.id
WHERE -- repeat for each map entry
t.name = ?1 AND t.value = ?2
OR t.name = ?3 AND t.value = ?4
...
Predicate tagsPredicate = criteriaBuilder.or(
request.getTags().entrySet().stream()
.map(e -> criteriaBuilder.and(
criteriaBuilder.equal(join.get(TagEntity_.name), e.getKey()),
criteriaBuilder.equal(join.get(TagEntity_.value), e.getValue())
))
.collect(Collectors.toList())
.toArray(new Predicate[0])
);
// add the tagsPredicate to your where clause, if the user has actually defined tag criteria