OneToMany:获取集合的大小

OneToMany: get size of collection

我正在使用 Spring Data JPA(内置 Hibernate)。我有两个实体测验和问题。它们之间的一对多关系。测验有问题列表。我如何获得有关测验和问题列表大小的信息?如果我这样写:

Quiz quiz = quizRepository.findOne(1);
int questionCount = quiz.getQuestions().size();

Hibernate 生成两个查询:

  1. Select 关于测验的信息
  2. Select 所有问题

但我只需要测验信息和问题的大小。

没有秒select怎么办?

您似乎需要将 FetchType=EAGER 添加到测验中的问题集合 class。

在这种情况下,所有问题(不仅是大小,还有所有内容)都将通过测验在一次查询中获取。

class Quiz {
...
@OneToMany(fetch = FetchType.EAGER, mappedBy = "quiz")
    public Set<Question> getStockDailyRecords() {
        return this.stockDailyRecords;
    }
 ...
}

如果您不需要在所有情况下都急切获取问题 - 您可以在查询中使用 fetch join:

@Query(value = "SELECT q FROM Quiz q LEFT JOIN FETCH q.questions",

在这种情况下,将从该查询中仅针对测验急切地获取问题。

您需要的是映射中带有 fetch 属性 的 JOIN 值,它将仅生成一个 select 查询并允许您获取大小你的 collection:

@OneToMany(fetch = FetchType.JOIN, mappedBy = "quiz")
public Set<Question> getQuestions() {
  ...
}

你可以在 Hibernate – fetching strategies examples 找到更多相关信息,它解释了所有的抓取策略、它们生成的查询以及它们之间的区别,你会看到:

With the fetch=”join” or @Fetch(FetchMode.JOIN) Hibernate generated only one select statement, it retrieves all its related collections when the Stock is initialized.

从您的问题中不清楚您是只想避免第二个 SQL 查询,还是想完全避免将问题加载到内存中。

如其他地方所述,您可以通过在关系本身或通过加载测验的 criteria/JPQLquery 指定获取模式来处理第一种情况。这将在一个 SQL 查询中加载所有内容,但您仍然会有加载问题的开销:加载问题在您的情况下可能不是问题,但对于大型数据集,如果您只需要计数,则开销可能相当大.

对于第二种情况,您有多种选择。 Hibernate 特定的非便携式解决方案是利用 Hibernate 的 @Formula 注释。

How to map calculated properties with JPA and Hibernate

class Quiz{
    @Formula("select count(*) from question where quiz_id = id")
    int numberOfQuestions;
}

或使用 Hibernate @LazyCollection(LazyCollectionOption.EXTRA) 属性,它允许您在不加载所有记录的情况下调用 size()

两者都可以在不将整个集合加载到内存的情况下为您提供问题数量。

第二种非 Hibernate 特定的可移植解决方案是创建一个视图 vw_quiz_summary_data,它具有相同的信息。然后,您可以将其映射为普通实体,并将其 link 作为一对一关系或 @SecondaryTable.

映射到测验

如果您愿意使用特定于 Hibernate 的注释,您可以执行以下操作:

class Quiz {
  @LazyCollection(LazyCollectionOption.EXTRA)
  @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "quiz", orphanRemoval = true)
  private Set<Question> questions;
}

注意 @LazyCollection(LazyCollectionOption.EXTRA).

的使用

现在,如果您这样做 quizRepsitory.findOne(...).getQuestions().size(),Hibernate 将触发查询 SELECT COUNT(...) FROM question WHERE quiz_id=?。但是,如果您执行 for(Question question : quizRepsitory.findOne(...).getQuestions()) { ... },Hibernate 将触发不同的查询:SELECT * FROM question WHERE quiz_id=?。此外,如果 quiz.questions 已经加载,则不会再次触发 COUNT 查询。使您不必将 Quiz 映射到 Question 两次,一次用于实际收集,另一次仅用于计数。