2013-12-13 3 views
1

Мне нужно построить запрос типа пересечения со следующими сущностями (уменьшенными для ясности).JPA Criteria API произвольное количество соединений/подзапросов

@Entity // and other @ stuff 
public class Member { 
    @Id 
    private Long id; 
    private String name; 
    ... 
} 

@Entity 
public class Program { 
    @Id 
    private Long id; 
    private Long programName; 
    private List<ProgramLevel> levels; 
    ... 
} 

@Entity 
public class ProgramLevel { 
    @Id 
    private Long id 
    private String levelName; 
} 

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

public class Membership { 
    @Id 
    private Long id; 
    private Long memberId;  // this is the member 
    private Long programId; // in which program is he 
    private Long programLevel; // and on what level 
    ... 
} 

Пример: У меня есть три программы по математике, английский, наука. Каждый из них имеет несколько уровней, например, MAth имеет алгебру, геометрию, английский язык имеет литературу, правописание и грамматику, у науки есть эксперименты и теория.

Кроме того, пример пользователя Джо имел бы математику: алгебру, английский: грамматические программы и уровни. Пример пользователя Макс мог бы иметь английский: литература. Таким образом, член может иметь несколько программ, но только один уровень для каждой программы.

Теперь мне нужно подсчитать или выбрать всех участников, которые соответствуют нескольким программам и некоторым уровням в них. Пример: Я хочу, чтобы все пользователи имели математику: алгебра или геометрия, а также английский: литература или грамматика и наука: теория.

Я не очень разбираюсь в материалах JPA, поэтому я застреваю.

В SQL я бы сделал пересечение. Как мне это сделать с JPA?

У меня есть что-то вроде этого:

HashMap<Long, List<ProgramLevel>> levels = new HashMap<Long, List<ProgramLevel>>(); 
// then I fetch the levels map. 

CriteriaBuilder cb = entityManager.getCriteriaBuilder(); 
CriteriaQuery<Long> criteriaQuery = cb.createQuery(Long.class); 
Root<Membership> root = query.from(Membership.class); 

// this is the part where I'm stuck. 

// I figured I could try with multiple inner joins? 
for(Map.Entry<Long, List<ProgramLevel>> curentLevel: levels.entrySet()) { 
    // cb.equal(root.join ??? what comes here? 
    // what what what? 
    // root.get("programId") = currentLevel.getKey() 
    // AND root.get("programLevelId") IN currentLevel.getValue() 

} 
... 

Как бы я это?

Помимо выполнения этого как нескольких внутренних объединений, я не знаю, можно ли это сделать как INTERSECT (db - PostgreSQL, если это имеет значение)?

Кроме того, я не уверен, где в этом вопросе я бы поставил условие только для того, чтобы получить отличное членство в этих условиях.

В качестве бонуса я должен будет создать запрос OR. Это одно, где я должен соответствовать всем программам, следующий нужен ЛЮБОЙ соответствие программы/уровня для члена, который будет включен в него. Но как только я это увижу, я надеюсь, что это будет один.

EDIT: возможный (псевдо) SQL выбрать будет выглядеть следующим образом:

SELECT count(membership) FROM membership m 
    WHERE (m.program == :program1 and m.programLevel in :programLevels1ArrayOfLevels) 
INTERSECT 
SELECT count(membership) FROM membership m 
    WHERE (m.program == :program2 and m.programLevel in :programLevels2ArrayOfLevels) 
INTERSECT... 
... /* this would go on for as many (Program, List<ProgramLevel>) pairs I have on input. Which is variable). 

Или что-то вроде этого:

SELECT count(membership) FROM membership m 
JOIN membership m1 ON (m1.program = :program1 AND m1.programLevel IN m1.programLevels1Aray) 
JOIN membership m2 ON (m2.program = :program2 AND m2.programLevel IN m1.programLevels2Aray) 
/* Continued to as many input pairs I have */ 
+1

Обычно вы делаете это, присоединяясь к одной таблице несколько раз с разными псевдонимами. JPA должна предложить способ сделать это, но мне блаженно не пришлось использовать его или API-интерфейс Criteria около года, поэтому я собираюсь указать вам примерно в правильном направлении, а затем убежать от криков для холмов. –

+0

Хм. И как я могу назвать имена этих объединений? Потому что я не знаю, сколько их там.Я не знаю, показывает ли это, но Java - это мой второй язык :) – Zlatko

+0

Не могли бы вы опубликовать [скрипку] (http://sqlfiddle.com/) или что-то о том, что вы хотите с точки зрения SQL? Также, сколько возможных объединений вам понадобится. Если вы только разбрасываете список значений, вы можете достичь чего-то похожего на [this] (http://stackoverflow.com/questions/403336/getting-a-query-intersection-in-jpa), зацикливая список значений и создавая объединения по требованию с помощью API критериев. Таким образом, вы также можете использовать псевдоним по запросу (например, 'q1',' q2', 'q3',' qn'). –

ответ

2
CriteriaBuilder cb = entityManager.getCriteriaBuilder(); 
CriteriaQuery<Tuple> criteriaQuery = cb.createQuery(Tuple.class); 
Root<Membership> root = query.from(Membership.class); 

Collection<Long> queredPrograms = levels.keySet() 
Predicate[] queryPredicates = new Predicate[queredPrograms.size()]; 
int i = 0; 
for(Long programId : queredPrograms) { 
    queryPredicates[i++] = cb.and(
     cb.equal(root.get("programId"), programId), 
     root.get("programLevel").in(levels.get(programId)) 
    ); 
} 
criteriaQuery.where(
    cb.and(
     cb.or(queryPredicates), 
     root.get("programId").in(queredPrograms) 
    ) 
); 

criteriaQuery.groupBy(root.get("programId")); 
criteriaQuery.select(cb.tuple(cb.count(cb.distinct(root.get("memberId"))), root.get("programId"))); 
TypedQuery<Tuple> countSelection = entityManager.createQuery(criteriaQuery); 

SQL эквивалент для критериев API будет выглядеть следующим образом (будет возвращать уникальный счетчик пользователей по каждому programId)

SELECT 
     COUNT(DISTINCT memberId), 
     programId 
FROM membership 
WHERE 
     programId in (:PROGRAMS_ID_LIST) 
     AND (
      (programId = :PROGRAM_ID AND programLevel IN (:PROGRAM_LEVEL_LIST)) 
      ... 
      OR (programId = :PROGRAM_ID_N AND programLevel IN (:PROGRAM_N_LEVEL_LIST)) 
    ) 
GROUP BY programId 

Если вы не хотите подсчитывать по каждой программе, просто удалите programId из группы и выберите предложения и измените OR на AND в where where. Я надеюсь, что это поможет вам.

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

  • Нет связанных вопросов^_^