真正动态的 JPA CriteriaBuilder

Posted

技术标签:

【中文标题】真正动态的 JPA CriteriaBuilder【英文标题】:Really dynamic JPA CriteriaBuilder 【发布时间】:2012-06-23 16:26:50 【问题描述】:

我需要创建一个“真正的”动态 JPA CriteriaBuilder。我收到带有声明的Map<String, String>。它看起来像:

name : John
surname : Smith
email : email@email.de

...more pairs possible

这是我实现的:

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> userRoot = query.from(User.class);
query.select(userRoot);

List<Predicate> predicates = new ArrayList<Predicate>();
Iterator<String> column = statements.keySet().iterator();
while (column.hasNext()) 

    // get the pairs
    String colIndex = column.next();
    String colValue = statements.get(colIndex);

    // create the statement
    Predicate pAnd = cb.conjunction();
    pAnd = cb.and(pAnd, cb.equal(userRoot.get(colIndex), colValue));
    predicates.add(pAnd);


// doesn't work, i don't know how many predicates i have -> can not address them
query.where(predicates.get(0), predicates.get(1), ...);

// doesn't work, because it is a list of predicates
query.where(predicates);

// doesn't work, because the actual predicate overwrites the old predicate
for (Predicate pre : predicates) 
     query.where(pre)

我尝试构建一个大的Predicate,它包含所有其他谓词并将其添加到query.where(),但谓词再次覆盖旧值。看起来不可能添加 Predicate 而不是更改谓词:-(

真正的项目更加复杂,因为有些对需要equal,而另一些需要like。这甚至还不够:可能还有一个包含or 的额外语句,例如type : 1;4;7。此处的值必须拆分并创建如下语句:

&lt;rest of statement&gt; AND (type = 1 OR type = 4 OR type = 7)

更新和解决方案 有两个列表,第一个列表用于 AND 效果很好。第二个列表包含 OR 语句,如预期:

final List<Predicate> andPredicates = new ArrayList<Predicate>();
final List<Predicate> orPredicates = new ArrayList<Predicate>();
for (final Entry<String, String> entry : statements.entrySet()) 
    final String colIndex = entry.getKey();
    final String colValue = entry.getValue();
    if (colIndex != null && colValue != null) 

        if (!colValue.contains(";")) 
            if (equals) 
                andPredicates.add(cb.equal(userRoot.get(colIndex), colValue));
             else 
                andPredicates.add(cb.like(userRoot.<String> get(colIndex), "%" + colValue + "%"));
            
         else 
            String[] values = colValue.split(";");
            for (String value : values) 
                orPredicates.add(cb.or(cb.equal(userRoot.get(colIndex), value)));
            
               
    


// Here goes the magic to combine both lists
if (andPredicates.size() > 0 && orPredicates.size() == 0) 
    // no need to make new predicate, it is already a conjunction
    query.where(andPredicates.toArray(new Predicate[andPredicates.size()]));
 else if (andPredicates.size() == 0 && orPredicates.size() > 0) 
    // make a disjunction, this part is missing above
    Predicate p = cb.disjunction();
    p = cb.or(orPredicates.toArray(new Predicate[orPredicates.size()]));
    query.where(p);
 else 
    // both types of statements combined
    Predicate o = cb.and(andPredicates.toArray(new Predicate[andPredicates.size()]));
    Predicate p = cb.or(orPredicates.toArray(new Predicate[orPredicates.size()]));
    query.where(o, p);


query.where(predicates.toArray(new Predicate[predicates.size()]));
users = em.createQuery(query).getResultList();

【问题讨论】:

【参考方案1】:

您可以将谓词数组传递给CriteriaBuilder,在进行时决定equallike。为此,构建一个列表并将列表的内容打包到单个 and 语句中的数组中。像这样:

final List<Predicate> predicates = new ArrayList<Predicate>();

for (final Entry<String, String> e : myPredicateMap.entrySet()) 

    final String key = e.getKey();
    final String value = e.getValue();

    if ((key != null) && (value != null)) 

        if (value.contains("%")) 
            predicates.add(criteriaBuilder.like(root.<String> get(key), value));
         else 
            predicates.add(criteriaBuilder.equal(root.get(key), value));
        
    


query.where(criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()])));
query.select(count);

如果您需要区分 andor,请使用两个列表。

【讨论】:

好的,我的 AND 语句有效。我创建了第二个列表并将我的 OR 语句存储在该列表中。最后,我做什么?将这两个列表复制到两种类型语句的 AND 中。我的orPredicates 列表包含正确的元素。即使我只将此列表添加到查询中,所有语句都用 AND 放在一起。 您可以创建析取(或)与创建合取(和)相同的方式 - 通过调用 CriteriaBuilder 上的 orand 方法。在这两种情况下,返回都是单个 Predicate - 因此,如果您没有某种复杂的分组,只需在两个结果谓词上调用 and 并在 where 中使用结果 也许我现在明白了,我更新了主要问题并发布了我的解决方案。【参考方案2】:

一种选择是使用具有可变数量参数的方法可以采用数组这一事实:

query.where(predicates.toArray(new Predicate[predicates.size()])); 

或者,您可以将它们组合成一个谓词(请注意,如果您不这样做,则不需要像示例中那样创建连词);:

Predicate where = cb.conjunction();
while (column.hasNext()) 
    ...
    where = cb.and(where, cb.equal(userRoot.get(colIndex), colValue));


query.where(where);

【讨论】:

【参考方案3】:

我一直认为,创建这样的解决方案就像重新发明自行车一样。 这里https://github.com/sasa7812/jfbdemo 是我的解决方案。它在 EclipseLink 和 Hibernate 上进行了测试 (EclipseLink 在生产中,我们在几个项目中使用了它以用于简单的案例)。 有时你只需要一个快速的解决方案来制作一个带有排序和过滤的数据表,没什么特别的。 它能够过滤和排序连接的字段,甚至是集合。 项目包含有关 Primefaces 的演示,展示了 FilterCriteriaBuilder 的能力。 在它的核心你只需要这个:

 public List<T> loadFilterBuilder(int first, int pageSize, Map<String, Boolean> sorts, List<FieldFilter> argumentFilters, Class<? extends AbstractEntity> entityClass) 
    FilterCriteriaBuilder<T> fcb = new FilterCriteriaBuilder<>(getEntityManager(), (Class<T>) entityClass);
    fcb.addFilters(argumentFilters).addOrders(sorts);
    Query q = getEntityManager().createQuery(fcb.getQuery());
    q.setFirstResult(first);
    q.setMaxResults(pageSize);
    return q.getResultList();

从数据库中获取结果。

我真的需要有人告诉我它很有用,并且在某个地方被用来继续我在这个库上的工作。

【讨论】:

【参考方案4】:

对于 Predicate,您可以创建新的 ArrayList,将所有谓词添加到列表中,最后您可以创建一个谓词数组并将列表转换为谓词数组并直接将其分配给 criteriabuilder.where(new Pridicate[])

【讨论】:

【参考方案5】:

你可以转换成 Predicate ... 如下

query.where( predicates.stream().toArray( Predicate[]::new ) );

【讨论】:

甚至更短的 query.where( predicates.toArray( Predicate[]::new ) );

以上是关于真正动态的 JPA CriteriaBuilder的主要内容,如果未能解决你的问题,请参考以下文章

java-jpa-criteriaBuilder使用入门

JPA / Hibernate:CriteriaBuilder - 如何使用关系对象创建查询?

如何告诉 JPA CriteriaBuilder 按返回的唯一数据列排序?

JPA CriteriaBuilder 中的自定义表达式

JPA Eclipselink子查询在where子句之间,CriteriaBuilder和元模型

JPA CriteriaBuilder in条件构建问题