2013-12-13 46 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 
    ... 
} 

例子:我有三個項目,數學,英語,科學。他們每個人都有一定的層次,如:有代數,幾何,英文有文學,拼寫和語法,科學有實驗和理論。

另外,示例用戶Joe將具有Math:代數,英語:語法程序和級別。用戶示例Max可能會有英文:文學。所以,會員可以有多個節目,但每個節目只有一個級別。

現在我需要計算或獲取匹配幾個程序和其中某些級別的所有成員。 示例:我希望所有擁有數學:代數或幾何的用戶,以及英文:文學或語法和科學:理論。

我並不是真的進入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查詢。這是我必須匹配所有程序的地方,下一個需要任何程序/級別匹配才能包含在其中。但是一旦我認識到這一點,我希望能夠自己找出一個。

編輯:可能的(僞)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應該提供一種方法來做到這一點,但我現在已經有一段時間沒有必要使用它或Criteria API了,所以我會指向大致正確的方向,然後逃離山峯尖叫。 –

+0

嗯。我怎麼去命名這些連接呢?因爲我不知道他們中有多少人。我不知道它是否顯示,但Java是我的第二語言:) – Zlatko

+0

你可以發佈一個[小提琴](http://sqlfiddle.com/)或什麼你想要的SQL?你還需要多少個可能的連接。如果你只是變量的值的列表,你可以通過循環的值列表來實現類似於[this](http://stackoverflow.com/questions/403336/getting-a-query-intersection-in-jpa)的東西使用Criteria 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將是這樣的,(將返回每個程序Id的唯一用戶數)

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 

如果您不想每個程序計數,只需從group by中刪除programId並選擇子句,然後在where子句中將OR更改爲AND。我希望這能幫到您。