Groovy 的多行字符串对HQL查询无效
使用HQL查询的时候你也可以进行分页和排序。要做的只是简单指定分页和排序参数作为一个散列在方法的末尾调用:
def results = Book.findAll("from Book as b where b.title like 'Lord of the%' order by b.title asc", [max:10, offset:20])
接下来的章节覆盖更多高级的GORM使用 包括 缓存、定制映射和事件。
GORM支持事件注册,只需要将事件作为一个闭包即可,当某个事件触发,比如删除,插入,更新。The following is a list of supported events下面就是所支持事件的列表:
beforeInsert
- 对象持久到数据之前执行 beforeUpdate
- 对象被更新之前执行 beforeDelete
- 对象被删除之前执行 afterInsert
- 对象持久到数据库之后执行 afterUpdate
- 对象被更新之后执行 afterDelete
- 对象被删除之后执行 onLoad
- 对象从数据库中加载之后执行 为了添加一个事件需要在你的领域类中添加相关的闭包。
当一个对象保存到数据库之前触发
class Person { Date dateCreateddef beforeInsert = { dateCreated = new Date() } }
当一个对象被更新之前触发
class Person { Date dateCreated Date lastUpdateddef beforeInsert = { dateCreated = new Date() } def beforeUpdate = { lastUpdated = new Date() } }
当一个对象被删除以后触发.
class Person { String name Date dateCreated Date lastUpdateddef beforeDelete = { new ActivityTrace(eventName:"Person Deleted",data:name).save() } }
当一个对象被加载之后触发:
class Person { String name Date dateCreated Date lastUpdateddef onLoad = { name = "I'm loaded" } }
上面的例子演示了使用事件来更新一个 lastUpdated
和 dateCreated
属性来跟踪对象的更新。事实上,这些设置不是必须的。通过简单的定义一个 lastUpdated
和 dateCreated
属性,GORM会自动的为你更新。
如果,这些行为不是你需要的,可以屏蔽这些功能。如下设置:
class Person { Date dateCreated Date lastUpdated static mapping = { autoTimestamp false } }
Grails 的域对象可以映射到许多遗留的模型通过 关系对象映射域语言。接下来的部分将带你领略它是可能的通过ORM DSL。
这是必要的,如果你高兴地坚持以约定来定义GORM对应的表,列名等。你只需要这个功能,如果你需要定制GORM 映射到遗留模型或进行缓存
自定义映射是使用静态的mapping
块定义在你的域类中的:
class Person { .. static mapping = {} }
类映射到数据库的表名可以通过使用 table
关键字来定制:
class Person { .. static mapping = { table 'people' } }
在上面的例子中,类会映射到 people
表来代替默认的 person
表.
同样,也是可能的定制某个列到数据库。比如说,你想改变列名例子如下:
class Person { String firstName static mapping = { table 'people' firstName column:'First_Name' } }
在这个例子中,你定义了一个column块,此块包含的方法调用匹配每一个属性名称 (in this case firstName
). 接下来使用命名的 column
, 来指定字段名称的映射.
GORM还可以通过DSL的type属性来支持Hibernate类型. 包括特定Hibernate的 org.hibernate.usertype.UserType 的子类, which allows complete customization of how a type is persisted. 比如,有一个 PostCodeType
你可以象下面这样使用:
class Address { String number String postCode static mapping = { postCode type:PostCodeType } }
另外如果你想将它映射到Hibernate的基本类型而不是Grails的默认类型,可以参考下面代码:
class Address { String number String postCode static mapping = { postCode type:'text' } }
上面的例子将使 postCode
映射到数据库的SQL TEXT或者CLOB类型.
See the Hibernate documentation regarding Basic Types for further information.
在关联中,你也有机会改变外键映射联系,在一对一的关系中,对列的操作跟其他常规的列操作并无二异,例子如下:
class Person { String firstName Address address static mapping = { table 'people' firstName column:'First_Name' address column:'Person_Adress_Id' } }
默认情况下 address
将映射到一个名称为 address_id
的外键. 但是使用上面的映射,我们改变外键列为 Person_Adress_Id
.
在一个双向的一对多关系中,你可以象前节中的一对一关系中那样改变外键列,只需要在多的一端中改变列名即可。然而,在单向关联中,外键需要在关联自身中(即一的一端-译者注)指定。比如,给定一个单向一对多联系 Person
和 Address
下面的代码会改变 address
表中外键:
class Person { String firstName static hasMany = [addresses:Address] static mapping = { table 'people' firstName column:'First_Name' addresses column:'Person_Address_Id' } }
如果你不想在 address
表中有这个列,可以通过中间关联表来完成,只需要使用 joinTable
参数即可:
class Person { String firstName static hasMany = [addresses:Address] static mapping = { table 'people' firstName column:'First_Name' addresses joinTable:[name:'Person_Addresses', key:'Person_Id', column:'Address_Id'] } }
默认情况下, Grails中多对多的映射是通过中间表来完成的. 以下面的多对多关联为例:
class Group { … static hasMany = [people:Person] } class Person { … static belongsTo = Group static hasMany = [groups:Group] }
在上面的例子中Grails将会创建一个 group_person
表包含外键 person_id
和 group_id
对应 person
和 group
表. 假如你需要改变列名,你可以为每个类指定一个列映射.
class Group { … static mapping = { people column:'Group_Person_Id' } } class Person { … static mapping = { groups column:'Group_Group_Id' } }
你也可以指定中间表的名称:
class Group { … static mapping = { people column:'Group_Person_Id',joinTable:'PERSON_GROUP_ASSOCIATIONS' } } class Person { … static mapping = { groups column:'Group_Group_Id',joinTable:'PERSON_GROUP_ASSOCIATIONS' } }
Hibernate 本身提供了自定义二级缓存的特性. 这就需要在 grails-app/conf/DataSource.groovy
文件中配置:
hibernate { cache.use_second_level_cache=true cache.use_query_cache=true cache.provider_class='org.hibernate.cache.EhCacheProvider' }
当然,你也可以按你所需来定制设置,比如,你想使用分布式缓存机制.
想了解更多Hibernate的二级缓存,参考 Hibernate documentation 相关文档.
假如要在映射代码块中启用缺省的缓存,可以通过调用 cache
方法实现:
class Person { .. static mapping = { table 'people' cache true } }
上面的例子中将配置一个读-写(read-write)缓存包括lazy和non-lazy属性.假如你想定制这些特性,你可以如下所示:
class Person { .. static mapping = { table 'people' cache usage:'read-only', include:'non-lazy' } }
就像使用Hibernate的二级缓存来缓存实例一样,你也可以来缓存集合(关联),比如:
class Person { String firstName static hasMany = [addresses:Address] static mapping = { table 'people' version false addresses column:'Address', cache:true } } class Address { String number String postCode }
上面的例子中,我们在 addresses 集合启用了一个读-写缓存,你也可以使用:
cache:'read-write' // or 'read-only' or 'transactional'
更多配置请参考缓存用法.
You can cache queries such as dynamic finders and criteria. To do so using a dynamic finder you can pass the cache
argument:
def person = Person.findByFirstName("Fred", cache:true)
Note that in order for the results of the query to be cached, you still need to enable caching in your mapping as discussed in the previous section.
You can also cache criteria queries:
def people = Person.withCriteria { like('firstName', 'Fr%') cache true }
下面是不同缓存设置和他们的使用方法:
read-only
- 假如你的应用程序需要读但是从不需要更改持久化实例,只读缓存或许适用. read-write
- 假如你的应用程序需要更新数据,读-写缓存或许是合适的. nonstrict-read-write
- 假如你的应用程序仅偶尔需要更新数据(也就是说,如果这是极不可能两笔交易,将尝试更新同一项目同时)并且时进行) ,并严格交易隔离,是不是需要一个nonstrict-read-write
可能是适宜的. transactional
- transactional
缓存策略提供支持对全事务缓存提供比如JBoss的TreeCache. 这个缓存或许仅仅使用在一个JTA环境,同时你必须在 grails-app/conf/DataSource.groovy
文件中的 hibernate
配置中 hibernate.transaction.manager_lookup_class
.
默认情况下GORM 类使用 table-per-hierarchy
来映射继承的. 这就有一个缺点就是在数据库层面,列不能有 NOT-NULL
的约束。如果你更喜欢 table-per-subclass
你可以使用下面方法:
class Payment { Long id Long version Integer amountstatic mapping = { tablePerHierarchy false } } class CreditCardPayment extends Payment { String cardNumber }
在祖先 Payment
类的映射设置中,指定了在所有的子类中,不使用 table-per-hierarchy
映射.
你可以通过DSL来定制GORM生成数据库标识,缺省情况下GORM将根据原生数据库机制来生成ids,这是迄今为止最好的方法,但是仍存在许多模式,不同的方法来生成标识。
为此,Hibernate特地定义了id生成器的概念,你可以自定义它要映射的id生成器和列,如下:
class Person { .. static mapping = { table 'people' version false id generator:'hilo', params:[table:'hi_value',column:'next_value',max_lo:100] } }
在上面的例子中,我们使用了Hibernate内置的'hilo'生成器,此生成器通过一个独立的表来生成ids.
想了解更多不同的Hibernate生成器请参考 Hibernate文档
注意,如果你仅仅想定制列id,你可以这样:
class Person { .. static mapping = { table 'people' version false id column:'person_id' } }
GORM支持复合标识(复合主键--译者注). 概念(标识由两个或者更多属性组成,这不是我们建议的方法,但是如果你想这么做,这也是可能的:
class Person { String firstName String lastNamestatic mapping = { id composite:['firstName', 'lastName'] } }
上面的代码将通过Person类的 firstName
和 lastName
属性来创建一个复合id。当你后面需要通过id取一个实例时,你必须用这个对象的原型:
def p = Person.get(new Person(firstName:"Fred", lastName:"Flintstone")) println p.firstName
To get the best performance out of your queries it is often necessary to tailor the table index definitions. How you tailor them is domain specific and a matter of monitoring usage patterns of your queries. 为得到最好的查询性能,通常你需要调整表的索引定义。如何调整它们是跟特定领域和要查询的用法模式相关的。使用GORM的DSL你可以指定那个列需要索引:
class Person { String firstName String address static mapping = { table 'people' version false id column:'person_id' firstName column:'First_Name', index:'Name_Idx' address column:'Address', index:'Name_Idx, Address_Index' } }
就像在 乐观锁和悲观锁 部分讨论的 , 默认情况下,GORM使用乐观锁和在每一个类中自动注入一个 version
属性,此属性将映射数据库中的一个 version
列.
如果你映射的是一个遗留数据库(已经存在的数据库--译者注), 这将是一个问题,因此可以通过如下方法来关闭这个功能:
class Person { .. static mapping = { table 'people' version false } }
如果你关闭了乐观锁 你将自己负责并发更新并且存在用户丢失数据的风险 (due to data overriding) 除非你使用 悲观锁
就像在 立即加载和延迟加载, 部分讨论的,默认情况下,GORM 集合使用延迟加载的并且可以通过 fetchMode
来配置, 但如果你更喜欢把你所有的映射都集中在 mappings
代码块中,你也可以使用ORM的DSL来配置获取模式:
class Person { String firstName static hasMany = [addresses:Address] static mapping = { addresses lazy:false } } class Address { String street String postCode }
在GORM中,one-to-one和many-to-one关联缺省是非延迟加载的.这在有很多实体(数据库记录-译者注)的时候,会产生性能问题,尤其是关联查询是以新的SELECT语句执行的时候. 此时你应该将one-to-one和many-to-one关联的延迟加载象集合那样进行设置:
class Person { String firstName static belongsTo = [address:Address] static mapping = { address lazy:true // lazily fetch the address } } class Address { String street String postCode }
这里我们设置 Person
的 address
属性为延迟加载.
正如 级联更新 这节描述的,控制更新和删除的主要机制是从关联一端到 belongsTo 静态属性的一端。
然而,通过cascade
属性,ORM DSL可以让你访问Hibernate的 transitive persistence 能力。
有效级联属性的设置包括:
获得级联样式更好的理解和用法的介绍,请阅读Hibernate文档的 transitive persistence章节
使用上述的值定义一个或多个级联属性(逗号分隔):
class Person { String firstName static hasMany = [addresses:Address] static mapping = { addresses cascade:"all,delete-orphan" } } class Address { String street String postCode }
在较早的章节看到可以(通过 embedded
属性) 把一个表分成多个对象。 你也可以通过Hibernate的自定义用户类型实现相同的效果。这不是领域类本身,而是java或者groovy类。所有这些类型都有一个继承自org.hibernate.usertype.UserType org.hibernate.usertype.UserType 的"meta-type"类。
Hibernate参考手册 有一些自定义类型资料,在这里我们将重点放在如何在Grails中映射。让我们看一个使用老式的(Java 1.5以前)枚举类型安全的领域类:
class Book { String title String author Rating ratingstatic mapping = { rating type: RatingUserType } }
我们所要做的是声明 rating
的枚举类型和在自定义映射UserType
中设置属性的类型。这是你想使用自定义类型所必须做的。你也可以使用其他列的设置,比如使用"column"来改变列名和使用"index"把它添加到index。
自定义类型不局限于只是一个列,他们可以映射到多列。在这种情况下,你必须在映射中明确地定义那列使用,因为Hibernate只能为一列使用属性名。 幸运的是,Grails可以为属性映射多列:
class Book { String title Name author Rating ratingstatic mapping = { name type: NameUserType, { column name: "first_name" column name: "last_name" } rating type: RatingUserType } }
上面的例子将为author
属性创建"first_name"和"last_name"列。You'll be pleased to know that you can also use some of the normal column/property mapping attributes in the column definitions. For example:
column name: "first_name", index: "my_idx", unique: true
The column definitions do not support the following attributes: type
, cascade
, lazy
, cache
, and joinTable
.
One thing to bear in mind with custom types is that they define the SQL types for the corresponding database columns. That helps take the burden of configuring them yourself, but what happens if you have a legacy database that uses a different SQL type for one of the columns? In that case, you need to override column's SQL type using the sqlType
attribute:
class Book { String title Name author Rating ratingstatic mapping = { name type: NameUserType, { column name: "first_name", sqlType: "text" column name: "last_name", sqlType: "text" } rating type: RatingUserType, sqlType: "text" } }
Mind you, the SQL type you specify needs to still work with the custom type. So overriding a default of "varchar" with "text" is fine, but overriding "text" with "yes_no" isn't going to work.
你可以使用像 list 方法中的参数来排序对象:
def airports = Airport.list(sort:'name')
当然,你也可以定义一个排序的声明:
class Airport { … static mapping = { sort "name" } }
必要的话你可以配置这个排序:
class Airport { … static mapping = { sort name:"desc" } }
另外,您也可以在关联中配置排序:
class Airport { … static hasMany = [flights:Flight] static mapping = { flights sort:'number' } }
Grails是构建在Spring的基础上的,所以使用Spring的事务来抽象处理事务编程,但GORM类通过 withTransaction 方法使得处理更简单,方法的第一个参数是Spring的 TransactionStatus 对象.
典型的使用场景如下:
def transferFunds = { Account.withTransaction { status -> def source = Account.get(params.from) def dest = Account.get(params.to)def amount = params.amount.toInteger() if(source.active) { source.balance -= amount if(dest.active) { dest.amount += amount } else { status.setRollbackOnly() } }
}
}
在上面的例子中,如果目的账户没有处于活动状态,系统将回滚事务,同时如果有任何异常抛出在事务的处理过程中也将会自动回滚。
假如你不想回滚整个事务,你也可以使用"save points"来回滚一个事务到一个特定的点。你可以通过使用Spring的 SavePointManager 接口来达到这个目的.
The withTransaction 方法为你处理begin/commit/rollback代码块作用域内的逻辑。
尽管约束是 验证 章节的内容, 但是在此涉及到约束也是很重要的,因为一些约束会影响到数据库的生成。
Grails通过使用领域类的约束来影响数据库表字段(领域类所对于的属性)的生成,还是可行的。
考虑下面的例子,假如我们有一个域模型如下的属性.
String name String description
默认情况下,在MySql数据库中,Grails将会定义这个列为...
column name | data type description | varchar(255)
但是,在业务规则中,要求这个领域类的description属性能够容纳1000个字符,在这种情况下,如果我们是使用SQL脚本,那么我们定义的这个列可能是:
column name | data type description | TEXT
现在我们又想要在基于应用程序的进行验证,_要求在持久化任何记录之前_,确保不能超过1000个字符。在Grails中,我们可以通过constraints. 来完成,我们将在领域类中新增如下的约束声明.
static constraints = { description(maxSize:1000) }
这个约束条件将会提供我们所需的基于应用程序的验证并且也将生成上述示例所示的数据库信息。下面是影响数据库生成的其他约束的描述。
如果 maxSize
或者 size
约束被定义, Grails将根据约束的值设置列的最大长度.
通常, 不建议在同一个的领域类中组合使用这些约束. 但是, 如果你非要同时定义 maxSize
和 size
约束的话, Grails将设置列的长度为 maxSize
约束和size上限约束的最少值. (Grails使用两者的最少值,因此任何超过最少值的长度将导致验证错误.)
如果定义了inList约束 (maxSize
和 size
未定义), 字段最大长度将取决于列表(list)中最长字符串的的长度. 以"Java"、"Groovy"和"C++"为例, Grails将设置字段的长度为6("Groovy"的最长含有6个字符).
如果定义了 max
、min
或者range
约束, Grails将基于约束的值尝试着设置列的精度. (设置的结果很大程度上依赖于Hibernate跟底层数据库系统的交互程度.)
通常来说, 不建议在同一领域类的属性上组合成双的min/max和range约束,但是如果这些约束同时被定义了,那么Grails将使用约束值中的最少精度值. (Grails取两者的最少值,是因为任意超过最少精度的长度将会导致一个验证错误.)
如果定义了scale约束, 那么Grails会试图使用基于约束的值来设置列的 标度(scale) . 此规则仅仅应用于浮点数值 (比如,java.lang.Float,java.Lang.Double, java.lang.BigDecimal及其相关的子类). (设置的结果同样也是很大程度上依赖于Hibernate跟底层数据库系统的交互程度.)
约束定义着数值的最小/最大值, Grails使用数字的最大值来设置其精度. 切记仅仅指定min/max约束中的一个,是不会影响到数据库的生成的 (因为可能会是很大的负值,比如当max是100), ,除非指定的约束值要比Hibernate默认的精度(当前是19)更高.比如...
someFloatValue(max:1000000, scale:3)
将产生:
someFloatValue DECIMAL(19, 3) // precision is default
但是
someFloatValue(max:12345678901234567890, scale:5)
将产生:
someFloatValue DECIMAL(25, 5) // precision = digits in max + scale
和
someFloatValue(max:100, min:-100000)
将产生:
someFloatValue DECIMAL(8, 2) // precision = digits in min + default scale