不初始化的情况下统计Hibernate集合大小

43
有没有一种方法可以在不初始化关联集合的情况下计算其大小?
例如:
Select count(p.children) from Parent p

(由于我的where子句更复杂,并且我的from子句是一个多态查询,所以我不能以其他方式完成这个任务)

谢谢。


请注意,使用contains在LazyCollection上进行存在性检查时,似乎对使用的键有很少的控制。这是一个小陷阱,因为您无法使用自然键来进行存在性检查。 - user1450430
3个回答

71

除了查询之外,可能的解决方案是使用 lazy="extra"(在XML符号中)映射children。这样,您可以使用任何需要的查询获取Parent,然后调用 parent.getChildren().size() 而不必加载整个集合(仅执行 SELECT COUNT 类型查询)。

如果使用注释,则为:

@OneToMany
@org.hibernate.annotations.LazyCollection(
org.hibernate.annotations.LazyCollectionOption.EXTRA
)
private Set<Child> children = new HashSet<Child>();

更新:来自《Java Persistence with Hibernate》第13.1.3章的引用:

如果您调用的任何方法不是标识符getter方法,则代理将被初始化;如果您开始遍历其元素或调用任何集合管理操作(例如size()contains()),则集合将被初始化。Hibernate提供了一个额外的设置,对于大型集合非常有用;它们可以映射为extra lazy. [...]

[如上所述映射],如果您调用size()contains()isEmpty(),则不再初始化集合——会查询数据库以检索必要的信息。如果它是MapList,则containsKey()get()操作也直接查询数据库。

因此,对于像上面映射的实体,您可以执行以下操作:

Parent p = // execute query to load desired parent
// due to lazy loading, at this point p.children is a proxy object
int count = p.getChildren().size(); // the collection is not loaded, only its size

你能再详细解释一下吗? - Varun Mehta
如果只需要 isEmpty(),Hibernate 不应该运行 COUNT(*) 查询。另请参阅:https://blog.jooq.org/2016/09/14/avoid-using-count-in-sql-when-you-could-use-exists/ - Lukas Eder
COUNT 被执行是因为 Hibernate 缓存了集合大小,这样 collection.isEmpty()collection.size() 都使用 cachedSize 而不是总是执行 COUNT(*)。但是,你关于 collection.isEmpty() 的想法是正确的,可以使用 EXISTS 代替。但是,EXTRA_LAZY 也不是真正的性能优化(我认为它更像是代码异味),因为如果你有一个非常大的集合,最好避免使用集合,而是使用分页查询。 - Vlad Mihalcea
@VladMihalcea:我不确定我是否理解了。这与分页无关,只与子记录的存在有关。我认为这是相当常见的用例,不是吗? - Lukas Eder
添加了LazyCollectionOption.EXTRA选项,这样即使在浏览集合时也不必完全加载整个集合。因此,它不会全部加载,而是像光标一样逐个加载每个元素。如果您只需要少量记录,则不会看到任何显着的性能下降。但是,如果加载许多项目,则性能将非常差。 - Vlad Mihalcea

2
您可以使用Session#createFilter,它是一种显式操作集合的HQL表单。例如,如果您提到了父母和孩子,那么如果您有一个人p,最基本的形式将是:
session.createFilter( p.getChildren(), "" ).list()

这将简单地返回一个子项列表。需要注意的是,返回的集合不是“实时”的,它与p没有任何关联。

有趣的部分来自第二个参数。这是一个HQL片段。例如,在这里,您可能需要:

session.createFilter( p.getChildren(), "select count(*)" ).uniqueResult();

您提到了一个where子句,因此您可能还需要:

session.createFilter( p.getChildren(), "select count(*) where this.age > 18" ).uniqueResult();

注意没有from子句。也就是说,from子句是从关联中隐含的。集合的元素被赋予别名“this”,因此您可以从HQL片段的其他部分引用它。


-2
你可以像这样做:
@Override
public FaqQuestions getFaqQuestionById(Long questionId) {
    session = sessionFactory.openSession();
    tx = session.beginTransaction();
    FaqQuestions faqQuestions = null;
    try {
        faqQuestions = (FaqQuestions) session.get(FaqQuestions.class,
                questionId);
        Hibernate.initialize(faqQuestions.getFaqAnswers());

        tx.commit();
        faqQuestions.getFaqAnswers().size();
    } finally {
        session.close();
    }
    return faqQuestions;
}

在您的控制器中使用faqQuestions.getFaqAnswers().size(),您将获得懒加载列表的大小,而无需获取列表本身。


1
这里到底发生了什么,为什么这样做会起作用?为什么要提交事务? - Audrius Meškauskas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接