【问题标题】:Can't avoid n+1 selects with many to many relationship in Grails无法避免在 Grails 中具有多对多关系的 n+1 选择
【发布时间】:2013-03-17 01:18:12
【问题描述】:

我正在使用 Grails 创建一个玩具问答网站,以了解该平台。 我有两个域,帖子和标签,它们之间有多对多的关系。我想打印带有标签的帖子列表。

我不能使用延迟获取,因为我会遇到 N+1 选择问题

我也不能使用 Eager fetch,因为它使用左连接,我无法正确分页结果。

因此我决定使用以下代码手动获取标签:


static def getList(params) {

        ArrayList questions = Question.list(params)

        def questionMap = [:]
        questions.each {
            questionMap.put(it.id, it)
        }

        if(questions.size()>0) {
            Tag.executeQuery('SELECT q.id, t FROM Tag t JOIN t.questions q \
                                WHERE q.id in ( :list ) ', [ list:questions.collect{ it.id } ] ).each { questionMap.get(it[0]).tags.add(it[1]) }
        }

        return questions
}

但是当我在视图中打印标签时:

<g:each in="${questions}" var="question">
   ${question.title} 
   <g:each in="${question.tags}" var="tag">
      ${tag?.text}
   </g:each>
</g:each>

无论如何都会为每个问题执行一个查询! 这里推荐的方法是什么?

【问题讨论】:

    标签: grails grails-orm grails-2.0


    【解决方案1】:

    您的代码的问题是您没有对Tag 查询的结果做任何事情。此外,为多对多关系设置连接类是一种更好的方法。例如,如果您看到 Spring Security Core 插件,您有 UserRole 和一个名为 UserRole 的连接类。 Here 是示例类。

    所以我给你的建议是:

    class Tag {
    ...
    }
    
    class Question{
    ...
    }
    
    class QuestionTag implements Serializable {
      Tag tag
      Question question
      static mapping = {
        id composite: ['tag','question']
        ...
      }
      //need to override equals and hashCode
    }
    

    要存储标签的结果,你可以为你的类添加一个临时字段:

    类问题{ 定义标签 静态瞬变 = ['标签'] //删除hasMany。 }

    您现在可以执行HQL,在问题列表中查找问题实例并设置tags 属性。而且由于您使用的是不返回单个类的 HQL,因此结果不会映射为 Tag 对象,因此访问方式略有不同。

    HQL 查询可以返回域类实例,或指定的数组 查询选择单个字段或计算值时的数据

    【讨论】:

    • 感谢您的回答。你是什​​么意思我不对标签查询的结果做任何事情?我确实将标签添加到相应问题的标签列表中,然后打印它们。在一个完美的世界里,这就是我想要对他们做的一切......还是我在这里错过了什么?
    • 你不会对Tag.executeQuery的结果无所作为,这就是我的意思。
    • 我确实执行了一个闭包来处理查询的结果: Tag.executeQuery( .... ).each { questionMap.get(it[0]).tags.add(it[1 ]) } 对吗?
    • 好吧,我错过了。但是您将其存储在questionMap 中,所以您的方法不应该返回questionMap 而不是question
    • question 和 questionMap 都包含对相同对象的引用。不过,我的错是代码可能不像它想象的那么容易阅读。
    【解决方案2】:

    你说的是

    “我也不能使用 Eager fetching,因为它使用左连接,而我 将无法正确分页结果。”

    您可以使用 session.createFilter 对关联进行分页。

    这个例子(版权 Burt Beckwith)来自Burt Beckwith's book Programming Grails from "Chapter 5, Hibernate , session.createFilter"

    // example from Burt Beckwith's book "Programming Grails", (c) Burt Beckwith
    class Branch {
        String name
        List visits
        static hasMany = [visits: Visit]
    
        List<Visit> getVisitsByPage(int pageSize, int pageNumber) {
            Branch.withSession { session ->
                session.createFilter(visits, '')
                        .setMaxResults(pageSize)
                        .setFirstResult(pageSize * pageNumber)
                        .list()
            }
        }
    }
    

    我推荐购买this book

    【讨论】:

    • 但是该代码检索集合(访问)的页面,而不是我的情况下具有相应访问的分支页面。另外,正如我在引用的文本中所说,您没有在那里使用渴望获取,所以我真的不明白。你的意思是可以创建一个类似的结构来使用过滤器解决我的问题吗?如果是这样,如果您能详细说明一下,我将不胜感激。
    • 哦,是的,我误解了你的要求。在某些复杂的情况下,当您想要优化 Hibernate 时,您可能必须手动分配关联。这个想法是在一个查询中获取所有子项并将它们放在内存映射中并在代码中手动进行连接。必须有一些关于如何以及何时应用这种丑陋解决方案的说明。在一些复杂的情况下,我不得不多次使用它来摆脱 N+1 问题。
    • “我也不能使用 Eager fetching,因为它使用左连接,我无法正确分页结果。”。我无法直接记住这种方法的问题所在。你能详细说明一下吗?
    • 假设您想要“N”个帖子及其相关标签。如果您使用连接,则具有 1 个以上关联标签的帖子将出现在与相关标签一样多的元组上。在进行查询之前,您不知道有多少帖子和标签,因此您无法知道需要多少个元组来检索“N”个不同的帖子。因此,您无法正确设置查询的限制。希望我自己解释一下;)
    猜你喜欢
    • 2012-05-19
    • 2020-03-08
    • 2011-12-07
    • 2015-11-28
    • 2022-08-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-24
    相关资源
    最近更新 更多