JPA Criteria API任意数量的连接/子查询

时间:2013-12-13 01:16:00

标签: java postgresql jpa join intersect

我需要使用以下实体构造一个交叉类型查询(为了清晰起见,减少了)。

@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有代数,几何,英语有文学,拼写和语法,科学有实验和理论。

此外,示例用户Joe将拥有Math:algebra,English:grammar programs and levels。示例用户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 个答案:

答案 0 :(得分: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);

标准API的SQL等价物将是这样的,(将返回每个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计数,只需从group by中删除programId并选择子句,并在where子句中将OR更改为AND。我希望这会对你有所帮助。