Can';t避免n+;1使用Grails中的多对多关系进行选择

Can';t避免n+;1使用Grails中的多对多关系进行选择,grails,gorm,grails-2.0,Grails,Gorm,Grails 2.0,为了学习这个平台,我正在用Grails创建一个玩具问答网站。 我有两个域,post和tag,它们之间有多对多的关系。我想打印带有标签的帖子列表 我不能使用延迟抓取,因为我会遇到N+1选择问题 我也不能使用即时抓取,因为它使用左连接,我无法正确地分页结果 因此,我决定使用以下代码手动获取标记: static def getList(params) { ArrayList questions = Question.list(params) def questio

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

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


我也不能使用即时抓取,因为它使用左连接,我无法正确地分页结果

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


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>

${question.title}
${tag?.text}
无论如何,每个问题都会执行一个查询!
这里推荐的方法是什么?

代码的问题是,您没有对
标记的查询结果执行任何操作。另外,对于多对多关系使用join类也是一种更好的方法。例如,如果您看到Spring安全核心插件,您就有
用户
角色
和一个名为
用户角色
的连接类。是示例类

所以我给你的建议是:

class Tag {
...
}

class Question{
...
}

class QuestionTag implements Serializable {
  Tag tag
  Question question
  static mapping = {
    id composite: ['tag','question']
    ...
  }
  //need to override equals and hashCode
}
要存储标记的结果,可以为类添加临时字段:

课堂提问{ def标签 静态瞬变=['tags'] //拆下hasMany。 }

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

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

你是说


“我也不能使用即时抓取,因为它使用左连接,而我 无法正确分页结果。”

可以使用session.createFilter为关联执行分页

这个例子(Burt Beckwith版权所有)来自“第5章,Hibernate,session.createFilter”

//来自伯特·贝克维思《编程Grails》一书的示例,(c)伯特·贝克维思
班支部{
字符串名
名单访问
静态hasMany=[访问:访问]
列表getVisitsByPage(int pageSize,int pageNumber){
Branch.withSession{会话->
会话.createFilter(访问“”)
.setMaxResults(页面大小)
.setFirstResult(页面大小*页面编号)
.list()
}
}
}

我建议买

谢谢你的回答。你说我不处理标记查询的结果是什么意思?我会将标签添加到相应问题的标签列表中,然后打印出来。在一个完美的世界里,这就是我想和他们做的一切。。。或者我遗漏了什么?你对
Tag.executeQuery
的结果什么都不做,这就是我的意思。我确实执行了一个闭包来处理查询的结果:Tag.executeQuery(..)。each{questionMap.get(it[0])。tags.add(it[1]),对吗?好的,我遗漏了each。但是您将其存储在
questionMap
中,因此您的方法不应该返回
questionMap
而不是
question
?questionMap和questionMap都包含对相同对象的引用。这是我的错,代码可能不像它应该的那么容易阅读。但是,代码检索集合的一页(访问),而不是像我的情况那样检索具有相应访问的分支的一页。另外,正如我在引用的文本中所说的,您没有在那里使用急切抓取,所以我真的不明白。你的意思是可以创建一个类似的结构来解决我使用过滤器的问题吗?如果是这样的话,如果你能详细说明一下,我将不胜感激。哦,是的,我误解了你的要求。在某些复杂的情况下,当您想要优化Hibernate时,可能必须手动分配关联。其思想是在一个查询中获取所有子项,并将它们放入内存映射中,然后在代码中手动进行连接。必须有一些关于如何以及何时应用这种丑陋解决方案的说明。在一些复杂的情况下,我不得不多次使用它来解决N+1问题。“我也不能使用即时抓取,因为它使用左连接,我无法正确地分页结果。”。我无法直接记得这种方法的问题是什么。你能详细说明一下吗?假设你想要“N”个贴子及其相关标签。如果使用联接,则具有1个以上关联标记的帖子将显示在与相关标记相同数量的元组上。在进行查询之前,您不知道有多少帖子和标记,因此您不知道检索“N”个不同帖子需要多少元组。因此,无法正确设置查询的限制。希望我能解释清楚;)
// 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()
        }
    }
}