【问题标题】:JPA2 Criteria queries on entity hierarchy关于实体层次结构的 JPA2 标准查询
【发布时间】:2010-09-28 13:06:20
【问题描述】:

假设我有以下实体域:

@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="TYPE")
public abstract class Entity1 {
//some attributes
}

@Entity 
@DiscriminatorValue("T1")
public class Entity2 extends Entity1 {
    @OneToMany(fetch=FetchType.EAGER, cascade = { CascadeType.ALL }, mappedBy="parent")
    @Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
    private Set<Entity1Detail> details = new HashSet<Entity1Detail>();
}

@Entity
public class Entity1Detail {
    @ManyToOne
    @JoinColumn(name="REF")
    private Entity2 parent;

    @Basic
    private Integer quantity;
}

@Entity
@DiscriminatorValue("T2")
public class Entity3 extends Entity1 {
//some other attributes
}

当我进行 JPQL 查询时:

select e from Entity1 e left join e.details d where d.quantity > 1

它运行良好(左连接;P)。但是,当我尝试使用 JPA2 Criteria API 构建相同的查询时:

CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery q = builder.createQuery();
Root r = q.from(Entity1.class);
q.select(r);
q.where(builder.gt(r.join("details", JoinType.LEFT).get("quantity"), 1));

我在“join”中得到 NPE,因为属性“details”不属于 Entity1(这实际上是真的,我必须在 Entity2.class 上进行选择)。问题是,当我必须使用 Criteria API 构建动态查询时,我对层次结构一无所知,我只是传递了一个类。

我知道 Criteria API 是类型安全的,但有没有办法解决这个问题?可能带有别名(就像我之前使用 Hibernate Criteria API,遍历带有别名的连接):

Criteria c = session.createCriteria(Entity1.class);
c.createAlias("details", "d");
c.add(Restrictions.ge("d.quantity", 1));

【问题讨论】:

  • 其实我有点错了。它是 JPA2 的休眠实现,允许您运行我上面提到的 JPQL 查询。如果您使用 OpenJPA(例如),这两种情况都会失败。

标签: jpa-2.0


【解决方案1】:

您的查询需要基于entity2.details。由于条件 API 是类型安全的,它会捕获 entity1 没有名为“details”的字段

CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery q = builder.createQuery();
Root r = q.from(Entity2.class);    // Must use subclass as root
q.select(r);
q.where(builder.gt(r.join("details", JoinType.LEFT).get("quantity"), 1));

由于 Entity2 扩展了 Entity1,您可以安全地将结果转换为父类型。例如:

CriteriaQuery<Entity1> q = builder.createQuery(Entity1.class);
Root r = q.from(Entity2.class);    // Must use subclass as root

将返回 Entity1 的列表

CriteriaQuery<Entity2> q = builder.createQuery(Entity2.class);
Root r = q.from(Entity2.class);    // Must use subclass as root

将返回 Entity2 的列表

编辑:

我想我误解了这里的目标。如果您想要所有 Entity1,除非它们是具有 details.quantity

您不能使用从 Entity1Detail 到 Entity1 的左连接,因为这不是严格类型安全的。相反,您需要以某种方式将 Entity2 加入 Entity1Detail。在这里使用的最佳工具可能是相关子查询

CriteriaQuery<Entity1> q = builder.createQuery(Entity1.class);
Root<Entity1> ent1 = q.from(Entity1.class);

SubQuery<Entity2> subq = q.subquery(Entity2.class);
Root<Entity2> ent2 = subq.from(Entity2.class);
Path<Integer> quantity = ent2.join("details", JoinType.LEFT).get("quantity");
Predicate lessThan = builder.lte(quantity,1);
Predicate correlatedSubqJoin = cb.equal(ent1,ent2)
subq.where(lessThan, correlatedSubqJoin);

q.select(ent1);
q.where(builder.exists(subq).not());

条件 API 不知道您是单表继承,因此您必须为所有继承策略编写查询,包括 Joined 继承策略。

【讨论】:

  • “你需要基于 entity2.details 进行查询”我知道,但我不能,因为创建上述类层次结构的重点是不知道你的对象是哪个具体类实例的。
  • 实际上,如果您仔细阅读我的帖子,我会准确地说出您在回复中所写的内容,并解释为什么这不是我想要的 ;)。
  • 抱歉,我误解了您要完成的工作。澄清一下:您想找到所有 Entity1,除非它们也是 Entity1Detail
猜你喜欢
  • 2014-10-26
  • 1970-01-01
  • 1970-01-01
  • 2014-07-11
  • 1970-01-01
  • 2014-01-30
  • 2019-05-02
  • 2020-04-17
  • 2015-10-15
相关资源
最近更新 更多