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