【问题标题】:How to pass a HashMap to CriteriaBuilder and add to Predicates list如何将 HashMap 传递给 CriteriaBuilder 并添加到 Predicates 列表
【发布时间】:2022-01-18 18:15:01
【问题描述】:

我目前在 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 个元素,则查询在应该返回 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?我不知道可以做些什么来解决这个问题。非常感谢一些帮助。

【问题讨论】:

    标签: jpa hashmap filtering predicate criteria-api


    【解决方案1】:

    与往常一样,用(大致)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
    

    【讨论】:

      猜你喜欢
      • 2015-02-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-05
      • 2021-11-21
      • 1970-01-01
      相关资源
      最近更新 更多