2012-06-21 10 views
33

「本当の」動的なJPA CriteriaBuilderを作成する必要があります。私は文でMap<String, String>を得ます。それは次のようになります。本当に動的なJPA CriteriaBuilder

ここ
name : John 
surname : Smith 
email : [email protected] 

...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のように含ま可能性がここに値のように分割し、文を作成する必要があります。UPDATEおよびソリューション は二つのリスト、最初のリストを手に入れた

<rest of statement> AND (type = 1 OR type = 4 OR type = 7)

ANDのためにうまくいく。第二のリストには含まれていOR exspectedのような文:

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(); 

答えて

43

あなたはあなたが行くようにequalまたはlikeの決定、CriteriaBuilderに述語の配列を渡すことができます。そのためには、リストを作成し、リストの内容を単一のandステートメントの配列にパックします。このように:あなたは、andor間distingiush二つのリストを使用する必要がある場合には

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); 

。あなたは、単一の述語(あなたがそれをしない場合はご注意にそれらを組み合わせることができ、また

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

+0

OK、私のANDステートメントが動作するためです。 2番目のリストを作成し、そのリストにORステートメントを格納します。結局、私は何をしますか?どちらのタイプのステートメントでも、リストtogehterの両方の結果をANDでコピーします。 私の 'orPredicates'リストには正しい要素が含まれています。このリストをクエリに追加するだけでも、すべてのステートメントはANDでまとめられます。 – Felix

+0

CriteriaBuilderで 'or'と' and'メソッドを呼び出すことによって、結合詞(and)を作成するのと同じ方法で(or)作成することができます。戻り値はどちらの場合も単一の「述語」です。何らかの複雑なグループ化がない場合は、結果の2つの述語に対して 'and'を呼び出し、' where'の結果を使用してください。 – kostja

+0

主な質問が更新され、私の解決策が掲載されました。 – Felix

16

1つのオプションは、可変個の引数を持つメソッドは、配列を取ることができるという事実を使用することです、あなたは)あなたの例のように組み合わせを作成する必要はありません;:

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

query.where(where); 
5

私はいつもそのようなソリューションの作成は自転車を再発明のようなものであることを考えています。 https://github.com/sasa7812/jfbdemoは私の解決策です。これはEclipseLinkとHibernate でテストされました(本番環境ではEclipseLink、単純なケースではいくつかのプロジェクトで使用していました)。 並べ替えとフィルタリングを使用してdataTableを作成するには、すばらしいソリューションは必要ありません。 結合されたフィールドやコレクションについても、フィルタリングと並べ替えができます。 プロジェクトには、FilterCriteriaBuilderの能力を示すPrimefaceのデモが含まれています。データベースから結果を得るために

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(); 
} 

:それあなたの心に はこれを必要とします。

本当に誰かがこのライブラリで作業を続けるのに便利で、どこかで使用されていると教えてくれる人が必要です。

0

述語の場合は、新しいArrayListを作成してすべての述語をリストに追加し、最後に述語配列を作成し、述語配列に変換してcriteriabuilderに直接割り当てることができます。新しい主格[])