Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Really dynamic JPA CriteriaBuilder

I need to create a "real" dynamic JPA CriteriaBuilder. I get an Map<String, String> with the statements. It looks like:

name : John surname : Smith email : [email protected]  ...more pairs possible 

Here is what i implement:

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

I tried to build a big Predicate, which contains all other predicates and add this to the query.where(), but again the predicates overwrites old values. Looks like there is no possibility to add a Predicate instead of change a Predicate :-(

The real project is even more complicated, because some pairs requires an equal and some other a like. And that is not even enough: There could a extra statement with or included like type : 1;4;7. Here the value have to split up and create a statement like:

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

UPDATE and SOLUTION Got two lists, first List for AND works well. Second list contains OR statements like 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(); 
like image 301
Felix Avatar asked Jun 21 '12 12:06

Felix


2 Answers

You can pass an array of predicates to the CriteriaBuilder, deciding on equal or like as you go. For this, build a list and pack the contents of the list into an array in a single and statement. Like this:

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

In case you need to distingiush between and and or, use two lists.

like image 81
kostja Avatar answered Sep 18 '22 13:09

kostja


One option is to use the fact that method with variable number of arguments can take an array:

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

Alternatively, you can combine them into a single predicate (note that if you don't do it, you don't need to create a conjunction as in your example);:

Predicate where = cb.conjunction(); while (column.hasNext()) {     ...     where = cb.and(where, cb.equal(userRoot.get(colIndex), colValue)); }  query.where(where); 
like image 37
axtavt Avatar answered Sep 18 '22 13:09

axtavt