Grails 1.2参考文档速读(7):GORM建模的其余事项及持久化基础

前面我们已经谈过关系建模,本篇我们将谈谈其他的建模问题,如Composition、继承、集合,以及GORM持久化的基础内容。
在关系建模中,我们已经知道如何建立one-to-many关系,其中要用到hasMany,缺省情况下,GORM会用集合类型Set来对应many,在数据库中则单独地对应一张表,其中有一个指向one的外键。集合除了可以应用到对象上,它同样还可以应用到基本类型,如String:
class Person {
        static hasMany = [nicknames:String]
    }
以上的规则同样适用于它,many端对应的表为(person_id, nickname)。你当然也同样可以对其进行自定义,同样也是使用ORM DSL:
static mapping = {
           hasMany joinTable:[name:'bunch_o_nicknames', 
                  key:'person_id', column:'nickname', 
                  type:"text"]       
    }
不要以为这种方式很罕见,实际上,在最新的 Shiro Plugin中,它为你产生的权限Domain Class中就使用了以上的映射方式。
除了支持对基本类型使用集合,Hibernate同样支持将一张表映射成多个对象,如Composition:
class Person {
     Address homeAddress
     Address workAddress
     static embedded = ['homeAddress', 'workAddress']
    }
    class Address {
     String number
     String code
    }
以上两个对象所对应的表为这两个对象属性的并集,其中的Address又被称为“值对象”,它没有自己的生命周期,由所属的Person实例决定。这里需要注意的是这些类要定义在同一文件中。
对于继承,Hibernate支持:
  • table-per-hierarchy
  • table-per-subclass
  • table-per-concrete-class
GORM缺省使用table-per-hierarchy,这种方式是另一种多对象对应一个表的情况,这种继承方式同样可通过ORM DSL可以使用其他方式。至于多态查询,理解起来也并不困难,即使用不同的子类进行查询,如xxx.list(),就列出不同子类的列表;而使用父类,则列出全部。子类在此充当了条件判断符的作用。这些内容并不复杂,详细请参见文档。
除了缺省使用的Set,GORM还支持其他的集合类型:SortedSet、列表和Map,只需要显示声明其类型即可:
class Author {
       SortedSet books
       static hasMany = [books:Book]
    }
不同的集合类型具有不同的含义,产生的数据库结构也不一样:
  • Set:无序、不重复,在数据库中,集合所对应的表我们在前面已经讲过了,包含主表的外键。
  • List:有序、可重复,在数据库中,集合所对应的表除了主表外键,还包含一个用来记录位置的索引。
  • Map:简单的值对,在数据库中,集合所对应的表除了主表外键,也会把它们包含在内。
在清楚这些集合的含义之后,了解它们对性能的影响就简单了:如果从包含集合的主表进行增加,一旦存在大量子表,性能必然下降。原因是:为了保持集合语义,Hibernate必须进行额外的查询操作。不信,你可以将SQL打印开关打开,然后创建包含Set和List的Domain Class,然后在Grails Console中保存这些Domain Class试试。
对于集合类型,Hibernate推荐使用bag,它的语义是:无序、可重复,这样Hibernate就不用再进行以上那些操作。遗憾的是。GORM目前并不支持Bag……
因此,典型的做法是:如果可预见有大量子表元素,那么就在子表上设置belongsTo,从子表端设置关系。据此可见,双向关联有很大的灵活性。
缺省情况下,Flush的时机是由Hibernate决定的,手工控制的方法则是在相应的方法中传入(flush:true),如p.save(flush:true)。
级联更新和删除:由定义belongsTo的另一端控制,它对级联策略的影响总结如下:
  • 双向“1对多”含belongsTo,在1端为“all”,多端为“none”
  • 单向“1对多”,1端为“save-update”
  • 双向“1对多”无belongsTo,1端为“save-update” ,多端为“none”
  • 单向“1对1”含belongsTo,含belongsTo的一端为“none”,另一端为“all”
关联缺省都是Lazy取,在某些情况下,就会导致n+1查询的情况。改进的方法是Eager取:
class Airport {
     String name
     static hasMany = [flights:Flight]
     //或static mapping = {
     // flight fetch:"join"
     //}
     static fetchMode = [flights:"eager"]
    }
但请注意,以上只适合在关联对象数目很小的情况下,由于Hibernate对此会使用左外联结,一旦数量很大,性能影响很大。
对于以上例子,还有另一种优化方式,即分批取。缺省情况下,每批的大小是1,这才是导致n+1查询的元凶。每批的大小既可以设在关联属性上:
class Airport {
     String name
     static hasMany = [flights:Flight]
     static mapping = {
      flight batchSize:10
     }
    }
也可以设置在对象上:
class Flight {
     …
     static mapping = {
      batchSize 10
     }
    }
除了实例级别的更新,GORM也支持批量更新,实际就是使用executeUpdate执行HQL,如Customer.executeUpdate(...)。
对于锁,一般是两种:乐观锁和悲观锁。乐观锁是在数据提交时才锁住数据,发挥作用,并发性高;悲观锁则是从一开始就锁住数据,直到提交完毕,并发性不高。
Hibernate是通过Version来支持乐观锁的,因此GORM也是使用这种方式,同时乐观锁是GORM的缺省锁形式。对于每一个Domain Class,GORM会缺省的添加一个version属性和id属性。对于乐观锁的使用:
def airport = Airport.get(10)
    try {
     airport.name = "Heathrow"
     airport.save(flush:true)
    }
    catch(org.springframework.dao.OptimisticLockingFailureException e) {
     // deal with exception
    }
乐观锁虽然效率高,但如果高并发场合要频繁的处理异常,这有可能抵消其本身的好处。此时,使用悲观锁似乎更合适。悲观锁的含义非常类似“select ... for update”,在GORM中是通过lock来获得的:
def airport = Airport.get(10)
    airport.lock() // 获得锁
    airport.name = "Heathrow"
    airport.save() //释放
但是,以上方式在get和lock之间还是给别人有可趁之机,这里则一开始就锁住:Airport.lock(10) 。除了这两种方法,我们还可以在查询中使用,如:
def airport = Airport.findByName("Heathrow", [lock:true])
    
    def airport = Airport.createCriteria().get {
     eq('name', 'Heathrow')
     lock true
    }

你可能感兴趣的:(职场,grails,休闲,gorm)