【问题标题】:JPA Criteria API - joining ElementCollection with WHERE condition on ElementCollection foreign keyJPA Criteria API - 在 ElementCollection 外键上使用 WHERE 条件加入 ElementCollection
【发布时间】:2020-07-04 19:17:17
【问题描述】:

我有一个实体 Document 和 enum Label (不是我的真实案例,我使用的是类比)。文档可以有一组标签。 标签的映射如下:

@Entity
public class Document {
    ...

    @ElementCollection(fetch = FetchType.EAGER)
    @Enumerated(EnumType.STRING)
    @Column(name = "labels")
    private Set<Label> labels = new HashSet<>();

    ...
}

这意味着标签被映射到具有两列(document_id,value)的单独表中,但在 Java 中它只是枚举

我需要选择没有任何列出标签的文档。 在 SQL 中它看起来像这样:

select D.id 
from document D left join label L 
on D.id = L.document_id and L.value in('label1','label2',...)
where L.document_id is null

但我不知道如何在 JPA Criteria API 中编写它。我不知道如何在标签表中表达外键。 JPA 谓词应该是这样的

CriteriaBuilder cd = ...
SetJoin<Object, Object> labelsJoin = root.joinSet("labels", JoinType.LEFT);
cb.and(labelsJoin .in("label1","label2"), cb.isNull(...???...)));

Here is my related SQL question

提前感谢您的建议。 卢卡斯

【问题讨论】:

  • 最初您应该提供有效的映射。 @ElementCollection 不应该与实体集一起使用。选择@OneToManyManyToMany。否则,您必须将标签更改为Set&lt;String&gt;。更多信息在这里callicoder.com/…
  • 标签是java枚举所以映射是正确的。对不起,我没有清楚地提到它。现在我的描述是固定的,更清楚了。

标签: java jpa join criteria-api


【解决方案1】:

这应该返回预期的结果,但查询语句有点不同

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

Subquery<Long> subquery = query.subquery(Long.class);
Root<Document> subRoot = subquery.from(Document.class);
Join<Document, Label> label = subRoot.join("labels", JoinType.INNER);

List<Label> labels = Arrays.asList(Label.LABEL_1, Label.LABEL_2);
subquery.select(subRoot.get("id")).where(
    cb.equal(root.get("id"), subRoot.get("id")), 
    label.in(labels)
);

query.select(root).where(cb.exists(subquery).not());

List<Document> result = entityManager.creteQuery(query).getResultList();

【讨论】:

  • 非常感谢。此解决方案有效。我还有一个问题。我已经构建了一个框架,可以将 url 查询参数映射到我正在使用的 JPA 查询谓词上。我可以使用这样的 query.select(root).where(cb.exists(subquery).not()).getRestriction() 从您的解决方案中提取谓词,所以我达到了我需要的目标。但是......任何想法如何在没有这些子查询的情况下更轻松地构建谓词?也许我要求太多,但至少想尝试一下。谢谢
  • 据我了解,您需要可重用的谓词吗?如果我的猜测是正确的,您可以在Specification 中编写您需要的所有子查询。我在这里回答了类似的问题stackoverflow.com/questions/60551105/…
  • 您对您提到的主题的回答与我的框架的工作方式完全一致。我只是想知道是否有办法为我的案例编写更简单的谓词。迄今为止唯一复杂的一个。所有其他谓词都是非常简单的 lambdas ......但这一个很难。
  • 我认为没有更简单的方法可以达到同样的效果
  • 我预料到了。还是谢谢你
【解决方案2】:

您应该可以轻松翻译 NOT IN 选项

select *
from document
where document.id not in (
    select document_id
    from label 
    where value in ('label1', 'label2')
)

进入标准 API

CriteriaQuery<Document> query = cb.createQuery(Document.class);
Root<Document> root = query.from(Document.class);
query.select(root);

Subquery<Long> subquery = query.subquery(Long.class);
Root<Document> subRoot = subquery.from(Document.class);
subquery.select(subRoot.<Long>get("id"));
Join<Document, Label> labelsJoin = subRoot.join("labels");  
subquery.where(labelsJoin.get("value").in("label1", "label2"));

query.where(cb.not(cb.in(root.get("id")).value(subquery)));

您可能需要调整 DocumentLabel 之间的连接。

【讨论】:

  • 我认为它接近解决方案,但由于labelsJoin.get(“value”)而崩溃。不过还是谢谢你
猜你喜欢
  • 2012-08-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-21
  • 2011-06-25
  • 2011-04-12
  • 1970-01-01
  • 2016-11-18
相关资源
最近更新 更多