Grails开发笔记 - 立即加载和延迟加载

使用ORM时,常常碰到N+1次查询的问题。hibernate采用立即加载(eager load)和延迟加载(lazy load)来解决这一问题,GROM建立在Hibernate的基础之上,理论上同样适用。但事实如何?

grails的官方文档中提到:默认情况下,GORM 集合使用延迟加载的并且可以通过fetchMode来配置或者是使用mapping来配置 。并给出了一段在domain中配置的样例代码。

但从我的使用经验来看,不推荐在domain类中配置延迟加载。原因如下:
1、在domain类中配置延迟加载是全局性的,有可能造成不需要开销。
2、目前在domain类中配置延迟加载存在Bug,只对one-to-many的关系才有效。

例如论坛的列表页面,需要显示topic的分页列表,其中每条topic需要显示作者的名字,topic最后回复帖子的作者。

按照官方文档,可以在Topic类中如下配置,查询结果为1条topic,理论上结果应该执行一条SQL语句:

  1. class   Topic       { 
  2.     String title 
  3.     String body 
  4.     Date createTime  =   new  Date() 
  5.     User author 
  6.     Integer viewNum  =   0 
  7.     Integer postNum  =   0 
  8.     Date lastUpdateTime 
  9.     User lastUpdateBy 
  10.     static  hasMany  =  [ posts : Post ] 
  11.     static  fetchMode  =  [author: ' eager ' , lastUpdateBy: ' eager ' ] 

但从SQL Log来看,Grails还是执行了3条SQL:

  1. select ... from topic where id  = ? 
  2. select ... from user where id  = ? 
  3. select ... from user where id  = ? 

也就是说在domain类中配置延迟加载存在对one-to-one, many-to-one是无效的。

如果这样配置:

  1. class   Topic       { 
  2.     String title 
  3.     String body 
  4.     Date createTime  =   new  Date() 
  5.     User author 
  6.     Integer viewNum  =   0 
  7.     Integer postNum  =   0 
  8.     Date lastUpdateTime 
  9.     User lastUpdateBy 
  10.      static  hasMany  =  [ posts : Post ] 
  11.      static  fetchMode  =  [posts: ' eager ' ] 
  12. }  

执行Topic.get(id)来加载1条Topic,执行结果为1条SQL:

  1. select ... from topic left outer join post on topic.id = posttopic_id where topic.id = ?  

也就是说在domain类中配置延迟加载存在对one-to-many是有效的。以上结论对使用mapping来配置也是一样的。

这无疑是一个尴尬的结论,适用全局立即加载的author和lastUpdateBy不能在domain类中通过配置事先,不适用全局立即加载的posts却可以。看来GROM对Hibernate的包装还存在问题。

目前的解决方法是不要在domain类中配置立即加载,而是在取数据的方法中按需要配置,例如Topic.list(fetch:[author:'eager', lastUpdateBy:'eager'])

另外,可以参考一下这里关于立即加载和N+1次查询性能的争论。

Tags: grails, hibernate

上一篇: Play with Play! - 一个Rails-like的Java框架
下一篇: Grails开发笔记 - 如何改变默认主页

相关文章

Trackbacks

点击获得Trackback地址,Encode: UTF-8

发表评论