本篇将迎来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合用。