如何将 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(请求中)中传递的 namevalue 获取机器实体,它们对应于 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