【问题标题】:Avoiding N+1 selects in Grails / Hibernate在 Grails / Hibernate 中避免 N+1 选择
【发布时间】:2012-05-19 09:35:27
【问题描述】:

更新:可能与 2.0.1 中的这个 Grails 错误有关: http://comments.gmane.org/gmane.comp.lang.groovy.grails.user/125400

更新 2:这让我最接近,但分页不起作用。在这种情况下,所有的 A 都被返回,每个 A 都有它们的 B 列表,每个 B 都有它的 C。只要我添加了 offset 和 max,事情就会变得一团糟。例如,在这种情况下,不会返回 10 个 A。可能会返回 1 个 A 和 9 个 B。

def results = A.list([fetch:[bees: "eager"], offset: 0, max: 10]);

问题:我想在一个查询中加载所有 A、它们关联的 B 和 B 的关联 C。在一个查询中检索所有 A 及其关联的 B 非常简单。我不确定如何将所有 B 的 C 作为同一个查询的一部分加载。按照设计,从 B -> C 的默认映射应该是急切的,因为我在加载 B 时总是需要 C。我认为设置此映射可以解决问题。

class A {
    static hasMany = [
            bees:  B,
    ]
}

class B {
    C c;

    static belongsTo = [a: A]

    // also tried this and every possible combination
    //static fetchMode = [c: 'eager']

    static mapping = {
        c fetch: 'join'
        // c lazy: 'false'
    }
}

class C {
    String someField;
}

这是我的查询:

def results = A.executeQuery("from A a left join fetch a.bees",
                     [max: pageSize, offset: offset]);

如果我现在迭代结果:

for (A a in results) {
   for (B b in a.bees) {
      println "B: " + b; // this is OK, B is already loaded
      println "B's C: " + b.c.someField; // C not loaded
   }
}

当我迭代结果时,b.c.someField 行将导致每个 B 执行一个“选择”。这很糟糕,我想尽可能避免这种情况。我发布了一个解决方案作为答案,它使用带有查找图的第二个查询,但必须有更好的方法。

我在“更新 2”中发布的查询让我非常接近。事实上,在我使用分页(使用偏移量/最大值)之前它工作得很好。 Grails 用户指南提到“fetch: 'join' 可能会导致带有偏移量/最大值的查询出现问题,但它没有详细说明。注意:为了让这个查询正常工作,我必须禁用休眠查询缓存,如由于 Grails 2.0.1 中的错误,“更新 1”。

有什么想法吗?

【问题讨论】:

  • 您是否尝试过不走.executeQuery() 路线而只使用内置的GORM 方法和对象图遍历?类似A.bees.each {/* do stuff with a.b.c */}
  • def bees = B.list(fetch:[c: "eager"]);
  • 更新了我的问题以包含似乎也不起作用的 fetchMode。
  • 两件事:a.)如果这是您链接到的错误,那么这就是答案:“这是一个错误”。否则,b.) 你到底想要什么?到目前为止,您共享的两段“get the bees”代码做了两件不同的事情。
  • 这个错误是一部分。通过禁用休眠查询缓存,现在 B.list() 和 B.findAll() 将尊重 B 中的映射并急切加载 C。虽然这不是最初的问题,但是如果没有解决这个更基本的问题,我没有希望找到我发布的更复杂问题的答案。

标签: hibernate grails orm


【解决方案1】:

一种解决方法是简单地获取所有 A 及其关联的 B。

一旦你有了它,你就可以得到所有 C 的 ID 而无需做任何查询:

def cIds = [] as Set;

for (A a : results) {
    for (B b : a.bees) {
        // Note: getCId() is a special method which does not cause a query
        cIds.add(b.getCId());
    }
}

获得所有 C ID 后,您可以使用第二个查询从数据库中获取这些 ID,并将它们存储到地图中以供以后查找:

def cMap = [:]
def cees = C.getAll(cIds.toList());
for (C c : cees) {
    cMap[c.id] = c;
}

现在,您可以遍历原始结果集以打印出 C 的字段:

for (A a : results) {
    for (B b : a.bees) {
        println "The meaning of life is " + cMap[b.getCId()].someField;
    }
}

查询总数 = 2(一个用于原始左连接,第二个用于获取所有 C)

如果有人有更好的解决方案,请发布。

【讨论】:

    【解决方案2】:

    根据 Grails 的文档,GORM 默认会延迟加载。这是文档的链接http://grails.org/doc/1.1/guide/5.%20Object%20Relational%20Mapping%20(GORM).html

    我不清楚你在这里问什么

    【讨论】:

    • 我想在一个查询中加载所有 A、它们关联的 B 和 B 的关联 C。这在普通 SQL 中非常简单——跨 3 个表的简单 3 路连接。
    • 我尝试更新原始问题以使其更清晰。
    猜你喜欢
    • 2015-11-28
    • 1970-01-01
    • 1970-01-01
    • 2011-12-07
    • 2011-07-12
    • 1970-01-01
    • 2012-09-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多