GORM的高级特性及其他

孙清野
2023-12-01

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

 类似资料: