不再使用GORM/Hibernate集合?
Burt Beckwithhttp://burtbeckwith.com/ 先生讲述了在 Grails中提高GORM的性能。他的演讲在 http://www.infoq.com/presentations/GORM-Performance
他的观点是在你的域对象中不使用集合,因为在对象被添加之前,集合可能需要从数据库中被完全加载。
我决定在我的工作项目中使用它。这个项目有18个域类。我遇到了一些问题,这些问题让我头疼几个小时,然后我才解决这些问题。更新整个项目从“hasMany/belongsTo“风格到没有集合风格它花费了我半天的时间 。这里是一个非常简单的域类,采用典型的 GORM 一对多的级联关系。
购物车程序示例
让我们用一个购物车示例代码开始。我们有一个购物车对象包含许多Item对象。
典型的GORM 关系
grails-app\domain\shopping\Cart.groovy
package shopping
class Cart {
String name
static hasMany = [ items : Item ]
static mapping = {
sort "name"
items sort:"name"
}
static constraints = {
name nullable:false, blank:false, unique:true
}
String toString() {
"Cart $name"
}
}
grails-app\domain\shopping\Item.groovy
package shopping
class Item {
String name
Integer quantity
static belongsTo = [ cart : Cart ]
static mapping = {
sort "name"
}
static constraints = {
name nullable:false, blank:false, unique:['cart']
quantity nullable:false
}
String toString() {
"$quantity $name"
}
}
grails-app\conf\BootStrap.groovy
import shopping.*
class BootStrap {
def init = { servletContext ->
def cart = new Cart(name:"alpha").save()
cart.addToItems new Item(name:"apples", quantity:10)
cart.addToItems new Item(name:"milk", quantity:2)
cart.addToItems new Item(name:"bread", quantity:3)
cart.save()
}
def destroy = {
}
}
删除 hasMany 和 belongsTo
现在,遵照Burt Beckwith的建议,我们删除 belongsTo 和 hasMany 关系,并且在子类中嵌入父对象
现在我们的代码看起来像这样:
BelongsTo 和HasMany 被删除
grails-app\domain\shopping\Cart.groovy
package shopping
class Cart {
String name
static mapping = {
sort "name"
}
static constraints = {
name nullable:false, blank:false, unique:true
}
String toString() {
"Cart $name"
}
}
grails-app\domain\shopping\Item.groovy
package shopping
class Item {
String name
Integer quantity
Cart cart
static mapping = {
sort "name"
}
static constraints = {
name nullable:false, blank:false, unique:['cart']
quantity nullable:false
}
String toString() {
"$quantity $name"
}
}
grails-app\conf\BootStrap.groovy
import shopping.*
class BootStrap {
def init = { servletContext ->
def cart = new Cart(name:"alpha").save()
new Item(cart:cart, name:"apples", quantity:10).save()
new Item(cart:cart, name:"milk", quantity:2).save()
new Item(cart:cart, name:"bread", quantity:3).save()
}
def destroy = {
}
}
这是所有的修改。
在 Cart.groovy 文件
- 删除了static hasMany = [ items : Item ]
- 从mappings中删除了 items sort:”name”
在 Item.groovy 文件
- 删除了 belongsTo
- 增加了 Cart cart
在 BootStrap.groovy 文件
- 修改了我们创建子类对象的方式.
- 从: cart.addToItems new Item(name:”apples”, quantity:10)
- 到 : new Item(cart:cart, name:”apples”, quantity:10)
新的问题和解决方案
我们已经完成了吗?当然不是–它并非那么简单。还有一些其他的问题需要解决::
- 子类对象items访问不再那么容易。
- 集合排序(在mapping中定义)不再发生。
- 不能删除父对象因为外键关系.
- 脚手架不再显示子类的对象.
让我们一次解决这些问题。
子类对象items访问不再那么容易
这个问题很容易修改。我们在Cart类中只是增加一个新的方法到父对象(Cart),它返回子对象的集合(Item):
grails-app\domain\shopping\Cart.groovy
....
def getItems() {
Items.findAllByCart(this)
}
....
集合排序(在mapping中定义)不再发生.
这个问题也很容易。我们只需要稍微修改上面的代码. 我们增加了一个排序的参数到findAllBy*方法中。
grails-app\domain\shopping\Cart.groovy
....
def getItems() {
Item.findAllByCart(this, [sort:"name"])
}
....
现在,我们能访问购物车中的items就像我们有了一个hasMany的关系.
println cart.items
不能删除父对象因为外键关系.
这是令人头痛的问题。不是因为它很困难,但它需要做一些小的研究找到一个优雅的解决方案.
这个问题是如果我做一个购物车的删除操作,它将会失败因为购物车中有items。在老的hasMany / belongsTo方案中将会自动级联删除items和cart。现在我们没有了,因此我们必须手动做删除.
这是我知道的最好的解决方案(如果有一个更好的方案让我知道)是在Cart对象上使用beforedelete事件对象。
在一个对象被删除之前,GORM会触发 beforeDelete事件。你能将代码放置在beforeDelete方法中,做你想做的任何事情。在我们的例子中,我们将删除子类的item对象。想看一看beforeDelete详细文档请访问 这里.
让我们修改Cart.groovy 的代码和增加beforeDelete方法.
grails-app\domain\shopping\Cart.groovy
....
def beforeDelete() {
Item.withNewSession { items*.delete() }
}
....
(这是我喜欢的Groovy的原因–一行代码能做那么多的工作!)
首先,我们建立了一个Hibernate的session。如果你读了关于beforeDelete的文档,你应该知道(这种情况)使用一个新的session为删除操作是为了避免产生StackOverflow异常。
其次,我们删除子类items的所有对象。我们使用groovy的扩展操作符调用列表中所有项的方法。
所以,这一行代码,当父对象(Cart)被删除时,所有的子对象(Item)将被删除。
脚手架不再显示子类的对象.
自从父类对象不再知道关于子类对象以后,默认的Grails的脚手架不再显示购物车中的项目。我不知道解决的方式,除了不使用脚手架。
最终Cart 和 Item 的代码
grails-app\domain\shopping\Cart.groovy
package shopping
class Cart {
String name
static mapping = {
sort "name"
}
static constraints = {
name nullable:false, blank:false, unique:true
}
String toString() {
"Cart $name"
}
def getItems() {
Item.findAllByCart(this, [sort:"name"])
}
def beforeDelete() {
Item.withNewSession { items*.delete() }
}
}
grails-app\domain\shopping\Item.groovy
package shopping
class Item {
String name
Integer quantity
Cart cart
static mapping = {
sort "name"
}
static constraints = {
name nullable:false, blank:false, unique:['cart']
quantity nullable:false
}
String toString() {
"$quantity $name"
}
}
我的观点
实现困难(Implementation Difficulty)
一旦我知道了怎样去实施这一方案,部署它在我的域类里那是相当简单。记住你需要更新任何使用addTo* 和removeFrom*方法的代码。
复杂性/维护(Complexity / Maintenance)
这种解决方案使我的应用程序理解更复杂吗?我不这么认为。一个有经验的Groovy编程者能够理解的cart.getItems()方法。beforeDelete需要一点时间来理解,但它很快就学会了。
同样使用belongsTo / hasMany也很复杂。请看GORM Gotchas Part 2。我不认为这一方案比使用belongsTo / hasMany更复杂.
性能(Performance)
还不知道。我没有在一个真实的应用中考虑过子类对象加载的性能问题。
我会再次这么做(Would I Do It Again)
肯定。我有一个项目崩溃了,因为它有超过20个对象和 超过30个关系。Hibernate 和GORM 导致各种异常,当时,对于如何解决问题我太缺乏经验。我考虑尝试使用“没有集合”技术,看看我是否能完成这个项目。我有一个好的预感,这个方法行不通,但我需要证明这一点。
英文原文:Implementing Burt Beckwith’s GORM Performance – No Collections
备注:英文原文回复更精彩,有兴趣的朋友可以看英文回复。