2012-06-21 2 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 Здесь значение нужно разделить и создать такое заявление:.

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

UPDATE и РЕШЕНИЕ Got два списка, первый список для И работает хорошо. Второй список содержит 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

Вы можете передать массив предикатов в CriteriaBuilder, принятие решения о equal или like, как вы идете. Для этого создайте список и упакуйте содержимое списка в массив в одном заявлении 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); 

В случае, если вам нужно distingiush между and и or использовать два списка.

+0

Хорошо, для моих И утверждений, которые работают. Я создаю второй список и сохраняю свои операторы OR в этом списке. В конце концов, что я делаю? Скопируйте оба списка togehter в И для обоих типов операторов. В моем списке 'orPredicates' содержатся правильные элементы. Даже когда я только добавляю этот список в запрос, все утверждения вместе с AND. – Felix

+0

Вы можете создать дизъюнкцию (или) так же, как вы создаете конъюнкцию (и) - путем вызова методов 'или' и' и' на CriteriaBuilder. Возврат - это один «Predicate» в обоих случаях, поэтому, если у вас нет какой-либо сложной группировки, просто вызовите 'and' на двух полученных Predicates и используйте результат в' where' – kostja

+0

Возможно, я получил его сейчас, я обновил главный вопрос и разместил мое решение. – Felix

16

Одним из вариантов является использование тот факт, что метод с переменным числом аргументов может принимать массив:

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

Я всегда думал, что создание решения так, как изобретать велосипед. Здесь https://github.com/sasa7812/jfbdemo - мое решение. Он был протестирован на EclipseLink и Hibernate (EclipseLink в производстве, мы использовали его в нескольких проектах для простых случаев). Иногда вам просто нужно быстрое решение для создания dataTable с сортировкой и фильтрацией, ничего необычного. Он может фильтровать и сортировать по объединенным полям и даже по коллекциям. Проект содержит демонстрацию на поверхностях, показывающую возможности 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(); 
} 

, чтобы получить результаты из базы данных.

Мне действительно нужно, чтобы кто-то сказал мне, что он полезен и используется где-то, чтобы продолжить мою работу над этой библиотекой.

0

Для предиката вы можете создать новый ArrayList, чтобы добавить весь предикат в список, а затем в конце вы можете создать предикатный массив и преобразовать список в предикатный массив и напрямую назначить его для criteriabuilder.where (new Pridicate [])

Смежные вопросы