本篇将迎来GORM的最后一部分:高级特性、编程事务,以及GORM和约束。
有过数据库编程经验的开发者对于触发器应该不会陌生,GORM中的事件则是类似的东西。毫无例外,GORM的事件实际就是Domain Class中定义的闭包:
class Person { ... def beforeDelete = { ... } }
事件的名字不能乱起,它们必须是:before/afterInsert、before/afterUpdate、before/afterDelete和onLoad其中之一。在写触发器时,我们应该小心避免死循环,在写事件处理时同样也要如此。对于事件内部的持久化操作,请使用withNewSession:
def beforeDelete() { ActivityTrace.withNewSession { new ActivityTrace( eventName:"Person Deleted", data:name).save() } }
同时也请注意,withNewSession中的新session,将会与老session共用一个jdbc连接。
自动时辍,即自动填写创建时间和更新时间,是应用中另一个常见需求。这一点在有了事件之后就非常容易实现了,然而GORM在这一方面走得更远,只要Domain Class中有名字为:lastUpdated和dateCreated的属性,GORM将会给它们自动赋值。当然,如果你嫌GORM碍事,也可以关掉它:
class Person { … static mapping = { autoTimestamp false } }
GORM固然不错,但如果无法自定义如表名、列名之类的那就让人感觉不舒服了。所幸,你可以通过ORM DSL来完成这些工作:
改表名:
class Person { .. static mapping = { table 'people' } }
改类型(Hibernate类型)和列名:
class Person { String firstName static mapping = { columns { firstName column:'First_Name', type:'text' } } }
one-to-one的外键:
class Person { Address address static mapping = { columns { address column:'Person_Adress_Id' } } }
one-to-many的外键:如果关系是双向的,方法同上;如果是单向的,则在many端进行:
class Person { static hasMany = [addresses:Address] static mapping = { columns { addresses column:'Person_Adress_Id' } } }
我们已经知道单向one-to-many会被映射成一个连接表,改变它的属性:
class Person { static hasMany = [addresses:Address] static mapping = { columns { addresses joinTable:[name:'Person_Addresses', key:'Person_Id', column:'Address_Id'] } } }
要改变many-to-many的连接表属性,方式与上面类似,不同的是需要在关系的双方都要进行。
缓存的配置是在grails-app/conf/DataSource.groovy中进行的,缺省已经打开,内容如下:
hibernate { cache.use_second_level_cache=true cache.use_query_cache=true cache.provider_class='net.sf.ehcache.hibernate.EhCacheProvider' }
其它配置则是在Domain Class中完成。
缓存实例:
static mapping = { //配置读写缓存 cache true //配置只读 cache usage:'read-only', include:'non-lazy' }
缓存关联:
static mapping = { //缺省是“read-write”, //还可以是'read-only' 或 'transactional' addresses column:'Address', cache:true }
缓存查询和Criteria:
Person.findByFirstName("Fred", [cache:true]) def people = Person.withCriteria { like('firstName', 'Fr%') cache true }
指定缓存的用途:
class Person { … static mapping = { cache usage:'read-only', include:'non-lazy' } }
缓存的其他用途:
- read-only,只读。用于只读场合。
- read-write,读写。事务隔离级别“read-committed”,用于对旧数据敏感,很少写的场景。
- nonstrict-read-write,不保证数据库和缓存的一致性,用于极少写,对旧数据不敏感的场景
- Transactional,事务隔离级别“可重复读”,用于对数据敏感,很少写的场景。只能用在JTA环境,且必须在DataSource.groovy中指定hibernate.transaction.manager_lookup_class
自定义继承策略,以下采用table-per-subclass策略:
class Payment { //在根上进行指定 Integer amount static mapping = { tablePerHierarchy false } } class CreditCardPayment extends Payment { String cardNumber }
自定义主键产生策略:
class Person { … static mapping = {//采用单独表记录主键产生值 id column:'person_id', generator:'hilo', params:[table:'hi_value' ,column:'next_value' ,max_lo:100] } }
组合键:
class Person { String firstName String lastName static mapping = { id composite:['firstName', 'lastName'] } }
使用:Person.get(new Person(firstName:"Fred", lastName:"Flintstone"))
索引(index指明列出现的索引名):
class Person { String firstName String address static mapping = { columns { firstName column:'First_Name', index:'Name_Idx' address column:'Address', index:'Name_Idx, Address_Index' } } }
关闭乐观锁:
class Person { static mapping = { version false } }
预先指定Eager:
class Person { static hasMany = [addresses:Address] static mapping = { columns { addresses lazy:false } } }
注意,Hibernate会为关联对象(1端)产生Proxy,使用instanceof操作符时需注意。GORM给Domain Class提供了instanceof动态方法,但不能完全解决该问题。
级联操作行为通过cascade属性设置:
class Person { String firstName static hasMany = [addresses:Address] static mapping = { addresses cascade:"all-delete-orphan" } }
cascade属性值如下:
- create,级联创建
- merge,合并分离关联的状态
- save-update,级联保存和更新
- delete,级联删除
- lock,在使用悲观锁时,级联锁住关联
- refresh,级联刷新
- evict,级联evict(类似GORM中的discard)
- all,级联ALL(除了delete-orphan)操作
- delete-orphan,在one-to-many关联中,当子对象从关联中被移走时(这是指在1端调用了removeFrom操作,把子对象从数组中删除),连带删除子对象
GORM同样也支持Hibernate的自定义类型,但建议少用,映射原则是“简单为上,够用就行”。在此就略过。
缺省排序:
- 查询时:Airport.list(sort:'name')
- 可以针对对象属性和关联设置,形式一样:
static mapping = { //或sort 'name' sort name:"desc" }
通过withTransaction可以自行控制事务:
Account.withTransaction { status -> …… if(dest.active) { dest.amount += amount } else { status.setRollbackOnly() } }
当异常出现,自动回滚;否则自动提交。在内部还可通过SavePointManager使用savePoint。Grails同样也支持声明性事务,在服务一节中会有介绍。
Grails的验证会单独介绍,在此只说明约束对于最终产生数据库的影响。
影响字符属性的有:
- inList,取其中字符串最大长度为该列大小
- maxSize,size和maxSize同时出现,取最小
- size
影响数字属性的有:min、max、range、scale,不建议min/max与range合用。