Grails 对象关联映射 (GORM) 一

转自:http://justjavac.iteye.com/blog/701445html

 

 

 

Domain 类是任何商业应用的核心。 他们保存事务处理的状态,也处理预期的行为。 他们经过关联联系在一块儿, one-to-one 或 one-to-many。java

GORM 是 Grails对象关联映射 (GORM)的实现。在底层,它使用 Hibernate 3 (一个很是流行和灵活的开源ORM解决方案),可是由于Groovy天生的动态性,实际上,对动态类型和静态类型二者都支持,因为Grails的规约,只须要不多的配置涉及Grails domain 类的建立。web

你一样能够在Java中编写 Grails domain 类。 请参阅在 Hibernate 集成上若是在Java中编写 Grails domain 类, 不过,它仍然使用动态持久方法。下面是GORM实战预览:spring

 

def book = Book.findByTitle("Groovy in Action")

book .addToAuthors(name:"Dierk Koenig") .addToAuthors(name:"Guillaume LaForge") .save()shell

 

5.1 快速入门指南

domain类可使用 create-domain-class 命令来建立:数据库

 

grails create-domain-class Person

这将在 grails-app/domain/Person.groovy 位置上建立类,以下:apache

 

class Person {      
}

 

若是在   DataSource  上设置 dbCreate属性为"update", "create" or "create-drop", Grails 会为你自动生成/修改数据表格。

你能够经过添加属性来自定义类:编程

 

class Person {      
        String name
        Integer age
        Date lastVisit
}

一旦你拥有一个 domain 类,能够尝试经过在 shell  console 上输入:api

 

grails console

这会载入一个交互式GUI,便于你键入Groovy命令。安全

5.1.1 CRUD基础

尝试执行一些基础的 CRUD (Create/Read/Update/Delete) 操做。

 

Create

为了建立一个 domain 类,可使用 Groovy new操做符, 设置它的属性并调用 save:

 

def p = new Person(name:"Fred", age:40, lastVisit:new Date())
p.save()

save 方法将使用底层的Hibernate ORM持久你的类到数据库中。

 

Read

Grails 会为你的domain类显式的添加一个隐式 id 属性,便于你检索:

 

def p = Person.get(1)
assert 1 == p.id

get 方法经过你指定的数据库标识符,从db中读取 Person对象。 你一样可使用 read 方法加载一个只读状态对象:

 

def p = Person.read(1)

在这种状况下,底层的 Hibernate 引擎不会进行任何脏读检查,对象也不能被持久化。注意,假如你显式的调用save 方法,对象会回到 read-write 状态.

 

Update

更新一个实体, 设置一些属性,而后,只需再次调用 save:

 

def p = Person.get(1)
p.name = "Bob"
p.save()

 

Delete

删除一个实体使用 delete 方法:

 

def p = Person.get(1)
p.delete()

 

5.2 GORM中进行Domain建模

当构建 Grails应用程序时,你必须考虑你要试图解决的问题域。 好比,你正在构建一个 Amazon 书店,你要考虑 books, authors, customers 和publishers 等等.

这些在GORM中被当作Groovy类 来进行建模,所以, Book 类可能拥有 title, release date,ISBN等等。 在后面章节将展现如何在GORM中进行domain建模。

建立domain类,你能够运行 create-domain-class ,以下:

 

grails create-domain-class Book

将会建立 grails-app/domain/Book.groovy类:

 

class Book {        
}

 

若是你想使用 packages 你能够把 Book.groovy类移动到 domain 目录的子目录下,并按照Groovy (和 Java)的 packaging 规则添加正确的   package  

上面的类将会自动映射到数据库中名为 book的表格 (与类名相同). 能够经过 ORM Domain Specific Language定制上面的行为。

如今,你能够把这个domain类的属性定义成Java类型。 例如:

 

class Book {
        String title
        Date releaseDate
        String ISBN
}

每一个属性都会被映射到数据库的列,列名的规则是全部列名小写,经过下划线分隔。 好比 releaseDate 映射到release_date列。 SQL类型会自动检测来自Java的类型 , 但能够经过 Constraints  ORM DSL定制。

5.2.1 GORM中的关联

关联定义了domain类之间的相互做用。除非在两端明确的指定,不然关联只存在被定义的一方。

 

5.2.1.1 One-to-one

one-to-one 关联是最简单的种类,它只是把它的一个属性的类型定义为其余domain类。 考虑下面的例子:

 

Example A

 

class Face {
    Nose nose
}
class Nose {       
}

在这种状况下, 拥有一个Face Nose的one-to-one单向关联。为了使它双向关联,须要定义另外一端,以下:

 

Example B

 

class Face {
    Nose nose
}
class Nose {       
        Face face
}

这就是双向关联。不过, 在这种状况下,关联的双方并不能级联更新。

考虑下这样的变化:

 

Example C

 

class Face {
    Nose nose
}
class Nose {       
        static belongsTo = [face:Face]
}

在这种状况下,咱们使用 belongsTo 来设置Nose "属于" Face。结果是,咱们建立一个Face并save 它,数据库将 级联 更新/插入 Nose:

 

new Face(nose:new Nose()).save()

上面的示例,face 和 nose都会被保存。注意,逆向 不为 true,并会由于一个临时的Face致使一个错误:

 

new Nose(face:new Face()).save() // will cause an error

belongsTo另外一个重要的意义在于,假如你删除一个 Face 实体, Nose 也会被删除:

 

def f = Face.get(1)
f.delete() // both Face and Nose deleted

若是没有belongsTo ,deletes 将被级联,并会获得一个外键约束错误,除非你明确的删除Nose:

// error here without belongsTo
def f = Face.get(1)
f.delete()

// no error as we explicitly delete both def f = Face.get(1) f.nose.delete() f.delete()

你能够保持上面的关联为单向,为了保证级联保存/更新,能够像下面这样:

 

class Face {
    Nose nose
}
class Nose {       
        static belongsTo = Face
}

注意,在这种状况下,咱们没有在belongsTo使用map语法声明和明确命名关联。Grails 会把它当作单向。.下面的图表概述了3个示例:

5.2.1.2 One-to-many

one-to-many 关联是,当你的一个类,好比 Author ,拥有许多其余类的实体,好比 Book 。 在Grails 中定义这样的关联可使用 hasMany :

 

class Author {
    static hasMany = [ books : Book ]

String name } class Book { String title }

在这种状况下,拥有一个单向的one-to-many关联。 Grails 将默认使用一个链接表映射这样的关联。

 

ORM DSL  容许使用外键关联做为映射单向关联的替代

对于 hasMany 设置,Grails将自动注入一个java.util.Set类型的属性到domain类。用于迭代集合:

 

def a = Author.get(1)

a.books.each { println it.title }

 

Grails中默认使用的fetch策略是 "lazy", 意思就是集合将被延迟初始化。 若是你不当心,这会致使   n+1 问题  

若是须要"eager" 抓取 ,须要使用 ORM DSL 或者指定当即抓取做为query的一部分

默认的级联行为是级联保存和更新,但不删除,除非 belongsTo 被指定:

 

class Author {
    static hasMany = [ books : Book ]

String name } class Book { static belongsTo = [author:Author] String title }

若是在one-to-many的多方拥有2个同类型的属性,必须使用mappedBy 指定哪一个集合被映射:

 

class Airport {
        static hasMany = [flights:Flight]
        static mappedBy = [flights:"departureAirport"]
}
class Flight {
        Airport departureAirport
        Airport destinationAirport
}

若是多方拥有多个集合被映射到不一样的属性,也是同样的:

 

class Airport {
        static hasMany = [outboundFlights:Flight, inboundFlights:Flight]
        static mappedBy = [outboundFlights:"departureAirport", inboundFlights:"destinationAirport"]
}
class Flight {
        Airport departureAirport
        Airport destinationAirport
}

 

5.2.1.3 Many-to-many

Grails支持many-to-many关联,经过在关联双方定义 hasMany ,并在关联拥有方定义 belongsTo :

 

class Book {
   static belongsTo = Author
   static hasMany = [authors:Author]
   String title
}
class Author {
   static hasMany = [books:Book]
   String name
}

Grails在数据库层使用一个链接表来映射many-to-many,在这种状况下,Author 负责持久化关联,而且是惟一能够级联保存另外一端的一方 。

例如,下面这个能够进行正常级联保存工做:

 

new Author(name:"Stephen King")
                .addToBooks(new Book(title:"The Stand"))
                .addToBooks(new Book(title:"The Shining"))           
                .save()

而下面这个只保存 Book而不保存 authors!

 

new Book(name:"Groovy in Action")
                .addToAuthors(new Author(name:"Dierk Koenig"))
                .addToAuthors(new Author(name:"Guillaume Laforge"))             
                .save()

这是所期待的行为,就像Hibernate,只有many-to-many的一方能够负责管理关联。

 

当前,Grails的 Scaffolding  特性 支持many-to-many关联, 你必须本身编写关联的管理代码

5.2.1.4 集合类型基础

除了关联不一样 domain 类外, GORM 一样支持映射基本的集合类型。好比,下面的类建立一个 nicknames 关联, 它是一个 String  Set 实体:

 

class Person {
    static hasMany = [nicknames:String]
}

GORM 将使用一个连接表,来映射上面的关联。你可使用joinTable参数来改变各式各样的链接表映射:

 

class Person {
    static hasMany = [nicknames:String]

static mapping = { hasMany joinTable:[name:'bunch_o_nicknames', key:'person_id', column:'nickname', type:"text"] } }

上面的示例映射到表后看上去像这样:

bunch_o_nicknames Table

---------------------------------------------
| person_id         |     nickname          |
---------------------------------------------
|   1               |      Fred             |
---------------------------------------------

5.2.2 GORM中的组合

除了 association 以外, Grails 支持组合概念。在这种状况下,并非把类映射到分离的表格,而是将这个类"embedded"到当前的表格内。 例如:

 

class Person {
        Address homeAddress
        Address workAddress
        static embedded = ['homeAddress', 'workAddress']
}
class Address {
        String number
        String code
}

所产生的映射看上去像这样:

 

若是你在 grails-app/domain目录中定义了一个单独的 Address类,你一样会获得一个表格。若是你不想这样,你能够 利用Groovy在单个文件定义多个类的能力,让 grails-app/domain/Person.groovy文件中的 Person类包含   Address类。

5.2.3 GORM中的继承

GORM 支持从抽象类的继承和具体持久化GORM实体的继承。例如:

 

class Content {
     String author
}
class BlogEntry extends Content {
    URL url
}
class Book extends Content {
    String ISBN
}
class PodCast extends Content {
    byte[] audioStream
}

上面的示例,咱们拥有一个 Content父类和各式各样带有更多指定行为的子类。

 

注意事项

在数据库层, Grails默认使用一个类一个表格的映射附带一个名为class的识别列,所以,父类 (Content) 和它的子类(BlogEntry, Book 等等.), 共享 相同的表格。

一个类一个表格的映射有个负面的影响,就是你 不能 有非空属性一块儿继承映射。 另外一个选择是使用每一个子类一个表格 ,你能够经过 ORM DSL启用。

不过, 过度使用继承与每一个子类一个表格会带来糟糕的查询性能,由于,过度使用连接查询。总之,咱们建议:假如你打算使用继承,不要滥用它,不要让你的继承层次太深。

 

多态性查询

继承的结果是你有能力进行多态查询。好比,在Content使用 list 方法,超类将返回全部Content子类:

 

def content = Content.list() // list all blog entries, books and pod casts
content = Content.findAllByAuthor('Joe Bloggs') // find all by author

def podCasts = PodCast.list() // list only pod casts

5.2.4 Sets, Lists 和 Maps

Sets对象

默认状况下,在中 GORM定义一个 java.util.Set 映射,它是无序集合,不能包含重复元素。 换句话,当你有:

 

class Author {
   static hasMany = [books:Book]
}

GORM会将books注入为 java.util.Set类型。问题在于存取时,这个集合的无序的,可能不是你想要的。为了定制序列,你能够设置为 SortedSet:

 

class Author {
   SortedSet books
   static hasMany = [books:Book]
}

在这种状况下,须要实现 java.util.SortedSet ,这意味着,你的Book类必须实现 java.lang.Comparable:

 

class Book implements Comparable {
   String title
   Date releaseDate = new Date()

int compareTo(obj) { releaseDate.compareTo(obj.releaseDate) } }

上面的结果是,Author类的中的books集合将按Book的releasedate排序。

 

List对象

若是你只是想保持对象的顺序,添加它们和引用它们经过索引,就像array同样,你能够定义你的集合类型为List:

 

class Author {
   List books
   static hasMany = [books:Book]
}

在这种状况下当你向books集合中添加一个新元素时,这个顺序将会保存在一个从0开始的列表索引中,所以你能够:

 

author.books[0] // get the first book

这种方法在数据库层的工做原理是:为了在数据库层保存这个顺序,Hibernate建立一个叫作books_idx的列,它保存着该元素在集合中的索引.

当使用List时,元素在保存以前必须先添加到集合中,不然Hibernate会抛出异常 (org.hibernate.HibernateException: null index column for collection):

 

// This won't work!
def book = new Book(title: 'The Shining')
book.save()
author.addToBooks(book)

// Do it this way instead. def book = new Book(title: 'Misery') author.addToBooks(book) author.save()

 

映射(Maps)对象

若是你想要一个简单的 string/value 对map,GROM能够用下面方法来映射:

 

class Author {
   Map books // map of ISBN:book names
}

def a = new Author() a.books = ["1590597583":"Grails Book"] a.save()

这种状况map的键和值都必须是字符串.

若是你想用一个对象的map,那么你能够这样作:

 

class Book {
  Map authors
  static hasMany = [authors:Author]
}

def a = new Author(name:"Stephen King")

def book = new Book() book.authors = [stephen:a] book.save()

static hasMany 属性定义了map中元素的类型,map中的key 必须 是字符串.

 

集合类型和性能

Java中的 Set 是一个不能有重复条目的集合类型. 为了确保添加到 Set 关联中的条目是惟一的,Hibernate 首先加载数据库中的所有关联. 若是你在关联中有大量的条目,那么这对性能来讲是一个巨大的浪费.

这样作就须要 List 类型, 由于Hibernate须要加载所有关联以维持供应. 所以若是你但愿大量的记录关联,那么你能够制做一个双向关联以便链接能在反面被创建。例如思考一下代码:

 

def book = new Book(title:"New Grails Book")
def author = Author.get(1)
book.author = author
book.save()

在这个例子中关联连接被child (Book)建立,所以没有必要手动操做集合以使查询更少和高效代码。因为Author有大量的关联的Book 实例,若是你写入像下面的代码,你能够看到性能的影响:

 

def book = new Book(title:"New Grails Book")
def author = Author.get(1)
author.addToBooks(book)
author.save()

5.3 持久化基础

关于Grails要记住的很重要的一点就是,Grails的底层使用 Hibernate 来进行持久化. 若是您之前使用的是ActiveRecord 或者 iBatis 您可能会对Hibernate的"session"模型感到有点陌生.

本质上,Grails自动绑定Hibernate session到当前正在执行的请求上.这容许你像使用GORM的其余方法同样很天然地使用 save  delete 方法.

 

 

5.3.1 保存和更新

下面看一个使用 save 方法的例子:

 

def p = Person.get(1)
p.save()

一个主要的不一样是当你调用save时候Hibernate不会执行任何SQL操做. Hibernate一般将SQL语句分批,最后执行他们.对你来讲,这些通常都是由Grails自动完成的,它管理着你的Hibernate session.

也有一些特殊状况,有时候你可能想本身控制那些语句何时被执行,或者用Hibernate的术语来讲,就是何时session被"flushed".要这样的话,你能够对save方法使用flush参数:

 

def p = Person.get(1)
p.save(flush:true)

请注意,在这种状况下,全部暂存的SQL语句包括以往的保存将同步到数据库。这也可让您捕捉任何被抛出的异常,这在涉及乐观锁高度并发的状况下是很经常使用的:

 

def p = Person.get(1)
try {
        p.save(flush:true)
}
catch(Exception e) {
        // deal with exception
}

5.3.2 删除对象

下面是 delete 方法的一个例子:

 

def p = Person.get(1)
p.delete()

默认状况下在执行delete之后Grails将使用事务写入, 若是你想在适当的时候删除,这时你可使用flush 参数:

 

def p = Person.get(1)
p.delete(flush:true)

使用 flush 参数也容许您捕获在delete执行过程当中抛出的任何异常. 一个广泛的错误就是违犯数据库的约束, 尽管这一般归结为一个编程或配置错误. 下面的例子显示了当您违犯了数据库约束时如何捕捉DataIntegrityViolationException:

 

def p = Person.get(1)

try { p.delete(flush:true) } catch(org.springframework.dao.DataIntegrityViolationException e) { flash.message = "Could not delete person ${p.name}" redirect(action:"show", id:p.id) }

注意Grails没有提供 deleteAll 方法,由于删除数据是discouraged的,并且一般能够经过布尔标记/逻辑来避免.

若是你确实须要批量删除数据,你可使用 executeUpdate 法来执行批量的DML语句:

 

Customer.executeUpdate("delete Customer c where c.name = :oldName", [oldName:"Fred"])

 

5.3.3 级联更新和删除

在使用GORM时,理解如何级联更新和删除是很重要的.须要记住的关键是 belongsTo 的设置控制着哪一个类"拥有"这个关联.

不管是一对一,一对多仍是多对多,若是你定义了 belongsTo ,更新和删除将会从拥有类到被它拥有的类(关联的另外一方)级联操做.

若是你 没有 定义 belongsTo 那么就不能级联操做,你将不得不手动保存每一个对象.

下面是一个例子:

 

class Airport {
        String name
        static hasMany = [flights:Flight]
}
class Flight {
        String number
        static belongsTo = [airport:Airport]
}

若是我如今建立一个 Airport 对象,并向它添加一些 Flight 它能够保存这个 Airport 并级联保存每一个flight,所以会保存整个对象图:

 

new Airport(name:"Gatwick")
         .addToFlights(new Flight(number:"BA3430"))
         .addToFlights(new Flight(number:"EZ0938"))
         .save()

相反的,若是稍后我删除了这个 Airport 全部跟它关联的 Flight也都将会被删除:

 

def airport = Airport.findByName("Gatwick")
airport.delete()

然而,若是我将 belongsTo 去掉的话,上面的级联删除代码就了. 不能工做. 为了更好地理解, take a look at the summaries below that describe the default behaviour of GORM with regards to specific associations.

 

设置了belongsTo的双向一对多

 

class A { static hasMany = [bees:B] }
class B { static belongsTo = [a:A] }

若是是双向一对多,在多的一端设置了belongsTo,那么级联策略将设置一的一端为"ALL",多的一端为"NONE".

 

单向一对多

 

class A { static hasMany = [bees:B] }
class B {  }

若是是在多的一端没有设置belongsTo单向一对多关联,那么级联策略设置将为"SAVE-UPDATE".

 

没有设置belongsTo的双向一对多

 

class A { static hasMany = [bees:B] }
class B { A a }

若是是在多的一端没有设置belongsTo的双向一对多关联,那么级联策略将为一的一端设置为"SAVE-UPDATE" 为多的一端设置为"NONE".

 

设置了belongsTo的单向一对一

 

class A {  }
class B { static belongsTo = [a:A] }

若是是设置了belongsTo的单向一对一关联,那么级联策略将为有关联的一端(A->B)设置为"ALL",定义了belongsTo的一端(B->A)设置为"NONE".

请注意,若是您须要进一步的控制级联的行为,您能够参见 ORM DSL.

5.3.4 当即加载和延迟加载

在GORM中,关联默认是lazy的.最好的解释是例子:

 

class Airport {
        String name
        static hasMany = [flights:Flight]
}
class Flight {
        String number
        static belongsTo = [airport:Airport]
}

上面的domain类和下面的代码:

 

def airport = Airport.findByName("Gatwick")
airport.flights.each {
        println it.name
}

GORM GORM将会执行一个单独的SQL查询来抓取 Airport 实例,而后再用一个额外的for each查询逐条迭代flights 关联.换句话说,你获得了N+1条查询.

根据这个集合的使用频率,有时候这多是最佳方案.由于你能够指定只有在特定的状况下才访问这个关联的逻辑.

 

配置当即加载

一个可选的方案是使用当即抓取,它能够按照下面的方法来指定:

 

class Airport {
        String name
        static hasMany = [flights:Flight]
        static mapping = {
                flight fetch:"join"
        }
}

在这种状况下 Airport 实例对应的 flights 关联会被一次性所有加载进来(依赖于映射). 这样的好处是执行更少的查询,可是要当心使用,由于使用太多的eager关联可能会致使你将整个数据库加载进内存.

 

关联也能够用   ORM DSL  将关联声明为 non-lazy

 

使用批量加载Using Batch Fetching

虽然当即加载适合某些状况,它并不老是可取的,若是您全部操做都使用当即加载,那么您会将整个数据库加载到内存中,致使性能和内存的问题.替代当即加载是使用批量加载.实际上,您能够在"batches"中配置Hibernate延迟加载. 例如:

 

class Airport {
        String name
        static hasMany = [flights:Flight]
        static mapping = {
                flight batchSize:10
        }
}

在这种状况下,因为 batchSize 参数,当您迭代 flights 关联, Hibernate 加载10个批次的结果. 例如,若是您一个Airport 有30个s, 若是您没有配置批量加载,那么您在对Airport的查询中只能一次查询出一个结果,那么要执行30次查询以加载每一个flight. 使用批量加载,您对Airport查询一次将查询出10个Flight,那么您只需查询3次. 换句话说, 批量加载是延迟加载策略的优化. 批量加载也能够配置在class级别:

 

class Flight {
        …
        static mapping = {
                batchSize 10
        }
}

5.3.5 悲观锁和乐观锁

乐观锁

默认的GORM类被配置为乐观锁。乐观锁实质上是Hibernate的一个特性,它在数据库里一个特别的 version 字段中保存了一个版本号.

version 列读取包含当前你所访问的持久化实例的版本状态的 version 属性:

 

def airport = Airport.get(10)

println airport.version

当你执行更新操做时,Hibernate将自动检查version属性和数据库中version列,若是他们不一样,将会抛出一个StaleObjectException 异常,而且当前事物也会被回滚.

这是颇有用的,由于它容许你不使用悲观锁(有一些性能上的损失)就能够得到必定的原子性。由此带来的负面影响是,若是你有一些高并发的写操做的话,你必须处理这个异常。这须要刷出(flushing)当前的session:

 

def airport = Airport.get(10)

try { airport.name = "Heathrow" airport.save(flush:true) } catch(org.springframework.dao.OptimisticLockingFailureException e) { // deal with exception }

你处理异常的方法取决于你的应用. 你能够尝试合并数据,或者返回给用户并让他们来处理冲突.

做为选择,若是它成了问题,你能够求助于悲观锁.

 

悲观锁

悲观锁等价于执行一个 SQL "SELECT * FOR UPDATE" 语句并锁定数据库中的一行. 这意味着其余的读操做将会被锁定直到这个锁放开.

在Grails中悲观锁经过 lock 方法执行:

 

def airport = Airport.get(10)
airport.lock() // lock for update
airport.name = "Heathrow"
airport.save()

一旦当前事物被提交,Grails会自动的为你释放锁. 但是,在上述状况下咱们作的事情是从正规的SELECT“升级”到SELECT ..FOR UPDATE同时其它线程也会在调用get()和lock()之间更新记录。

为了不这个问题,你可使用静态的lock 方法,就像get方法同样传入一个id:

 

def airport = Airport.lock(10) // lock for update
airport.name = "Heathrow"
airport.save()

这个只有 SELECT..FOR UPDATE 时候可使用.

 

尽管Grails和Hibernate支持悲观所,可是在使用Grails内置默认的 HSQLDB 数据库时 不支持。若是你想测试悲观锁,你须要一个支持悲观锁的数据库,例如MySQL.

你也可使用lock 方法在查询中得到悲观锁。例如使用动态查询:

 

def airport = Airport.findByName("Heathrow", [lock:true])

或者使用criteria:

 

def airport = Airport.createCriteria().get {
        eq('name', 'Heathrow')
        lock true
}

 

5.4 GORM查询

GORM提供了从动态查询器到criteria到Hibernate面向对象查询语言HQL的一系列查询方式.

Groovy经过 GPath 操纵集合的能力, 和GORM的像sort,findAll等方法结合起来,造成了一个强大的组合.

可是,让咱们从基础开始吧.

 

获取实例列表

若是你简单的须要得到给定类的全部实例,你可使用 list 方法:

 

def books = Book.list()

list 方法支持分页参数:

 

def books = Book.list(offset:10, max:20)

也能够排序:

 

def books = Book.list(sort:"title", order:"asc")

这里,Here, the sort 参数是您想要查询的domain类中属性的名字,argument is the name of the domain class property that you wish to sort on, and the order 参数要么以argument is either asc for asc结束ending or要么以desc for desc结束ending.

 

根据数据库标识符取回

第二个取回的基本形式是根据数据库标识符取回,使用 get 方法:

 

def book = Book.get(23)

你也能够根据一个标识符的集合使用 getAll方法取得一个实例列表:

 

def books = Book.getAll(23, 93, 81)

5.4.1 动态查询器

GORM支持 动态查找器 的概念 . 动态查找器看起来像一个静态方法的调用,可是这些方法自己在代码中实际上并不存在.

而是在运行时基于一个给定类的属性,自动生成一个方法. 好比例子中的 Book 类:

 

class Book {
        String title
        Date releaseDate
        Author author
}                
class Author {
        String name
}

Book 类有一些属性,好比 title, releaseDate  author. 这些均可以按照"方法表达式"的格式被用于 findBy findAllBy 方法:

 

def book = Book.findByTitle("The Stand")

book = Book.findByTitleLike("Harry Pot%")

book = Book.findByReleaseDateBetween( firstDate, secondDate )

book = Book.findByReleaseDateGreaterThan( someDate )

book = Book.findByTitleLikeOrReleaseDateLessThan( "%Something%", someDate )

 

方法表达式

在GORM中一个方法表达式由前缀,好比 findBy 后面跟一个表达式组成,这个表达式由一个或多个属性组成。基本形式是:

 

Book.findBy([Property][Comparator][Boolean Operator])?[Property][Comparator]

用'?' 标记的部分是可选的. 每一个后缀都会改变查询的性质。例如:

 

def book = Book.findByTitle("The Stand")

book = Book.findByTitleLike("Harry Pot%")

在上面的例子中,第一个查询等价于等于后面的值, 第二个由于增长了 Like 后缀, 它等价于SQL的 like 表达式.

可用的后缀包括:

  • InList - list中给定的值
  • LessThan - 小于给定值
  • LessThanEquals - 小于或等于给定值
  • GreaterThan - 大于给定值
  • GreaterThanEquals - 大于或等于给定值
  • Like - 价于 SQL like 表达式
  • Ilike - 相似于Like,但不是大小写敏感
  • NotEqual - 不等于
  • Between - 于两个值之间 (须要两个参数)
  • IsNotNull - 不为null的值 (不须要参数)
  • IsNull - 为null的值 (不须要参数)

你会发现最后三个方法标注了参数的个数,他们的示例以下:

 

def now = new Date()
def lastWeek = now - 7
def book = Book.findByReleaseDateBetween( lastWeek, now )

books = Book.findAllByReleaseDateIsNull() books = Book.findAllByReleaseDateIsNotNull()

 

布尔逻辑(AND/OR)

方法表达式也可使用一个布尔操做符来组合两个criteria:

 

def books = 
    Book.findAllByTitleLikeAndReleaseDateGreaterThan("%Java%", new Date()-30)

在这里咱们在查询中间使用 And 来确保两个条件都知足, 可是一样地你也可使用 Or:

 

def books = 
    Book.findAllByTitleLikeOrReleaseDateGreaterThan("%Java%", new Date()-30)

At the moment此时, 你最多只能用两个criteria作动态查询, 也就是说,该方法的名称只能含有一个布尔操做符. 若是你须要使用更多的, 你应该考虑使用 Criteria  HQL.

 

查询关联

关联也能够被用在查询中:

 

def author = Author.findByName("Stephen King")

def books = author ? Book.findAllByAuthor(author) : []

在这里若是 Author 实例不为null 咱们在查询中用它取得给定 Author 的全部Book实例.

 

分页和排序

 list 方法上可用的分页和排序参数同样,他们一样能够被提供为一个map用于动态查询器的最后一个参数:

 

def books = 
  Book.findAllByTitleLike("Harry Pot%", [max:3, 
                                         offset:2, 
                                         sort:"title",
                                         order:"desc"])

5.4.2 条件查询

Criteria 是一种类型安全的、高级的查询方法,它使用Groovy builder构造强大复杂的查询.它是一种比使用StringBuffer好得多的选择.

Criteria能够经过 createCriteria 或者 withCriteria 方法来使用. builder使用Hibernate的Criteria API, builder上的节点对应Hibernate Criteria API中 Restrictions 类中的静态方法. 用法示例:

 

def c = Account.createCriteria()
def results = c {
        like("holderFirstName", "Fred%")
        and {
                between("balance", 500, 1000)
                eq("branch", "London")
        }
        maxResults(10)
        order("holderLastName", "desc")
}

 

逻辑与(Conjunctions)和逻辑或(Disjunctions)

如前面例子所演示的,你能够用 and { } 块来分组criteria到一个逻辑AND:

 

and {
        between("balance", 500, 1000)
        eq("branch", "London")
}

逻辑OR也能够这么作:

 

or {
        between("balance", 500, 1000)
        eq("branch", "London")
}

你也能够用逻辑NOT来否认:

 

not {
        between("balance", 500, 1000)
        eq("branch", "London")
}

 

查询关联

关联能够经过使用一个跟关联属性同名的节点来查询. 好比咱们说 Account 类有关联到多个 Transaction 对象:

 

class Account {
    …
    def hasMany = [transactions:Transaction]
    Set transactions
    …
}

咱们可使用属性名 transaction 做为builder的一个节点来查询这个关联:

 

def c = Account.createCriteria()
def now = new Date()
def results = c.list {
       transactions {
            between('date',now-10, now)
       }
}

上面的代码将会查找全部过去10天内执行过 transactions  Account 实例. 你也能够在逻辑块中嵌套关联查询:

 

def c = Account.createCriteria()
def now = new Date()
def results = c.list {
     or {
        between('created',now-10,now)
        transactions {
             between('date',now-10, now)
        }
     }
}

这里,咱们将找出在最近10天内进行过交易或者最近10天内新建立的全部用户.

 

投影(Projections)查询

投影被用于定制查询结果. 要使用投影你须要在criteria builder树里定义一个"projections"节点. projections节点内可用的方法等同于 Hibernate 的 Projections 类中的方法:

 

def c = Account.createCriteria()

def numberOfBranches = c.get { projections { countDistinct('branch') } }

 

使用可滚动的结果

Y你能够经过调用scroll方法来使用Hibernate的 ScrollableResults 特性:

 

def results = crit.scroll {
      maxResults(10)
}
def f = results.first()
def l = results.last()
def n = results.next()
def p = results.previous()

def future = results.scroll(10) def accountNumber = results.getLong('number')

下面引用的是Hibernate文档中关于ScrollableResults的描述:

 

结果集的迭代器(iterator)能够以任意步进的方式先后移动,而Query / ScrollableResults模式跟JDBC的PreparedStatement/ ResultSet也很像,其接口方法名的语意也跟ResultSet的相似.

不一样于JDBC,结果列的编号是从0开始.

 

在Criteria实例中设置属性

若是在builder树内部的一个节点不匹配任何一项特定标准,它将尝试设置为Criteria对象自身的属性。所以容许彻底访问这个类的全部属性。下面的例子是在Criteria Criteria实例上调用 setMaxResults  setFirstResult:

 

import org.hibernate.FetchMode as FM
        …
        def results = c.list {
                maxResults(10)
                firstResult(50)
                fetchMode("aRelationship", FM.EAGER)
        }

 

当即加载的方式查询

 Eager and Lazy Fetching当即加载和延迟加载 这节,咱们讨论了若是指定特定的抓取方式来避免N+1查询的问题。这个criteria查询也能够作到:

 

def criteria = Task.createCriteria()
def tasks = criteria.list{
     eq "assignee.id", task.assignee.id
     join 'assignee'
     join 'project'
     order 'priority', 'asc'
}

注意这个 join 方法的用法. This method indicates the criteria API that a JOIN query should be used to obtain the results.

 

方法引用

若是你调用一个没有方法名的builder,好比:

 

c { … }

默认的会列出全部结果,所以上面代码等价于:

 

c.list { … }

 

方法 描述

list 这是默认的方法。它会返回全部匹配的行。
get 返回惟一的结果集,好比,就一行。criteria已经规定好了,仅仅查询一行。这个方法更方便,省得使用一个limit来只取第一行令人迷惑。
scroll 返回一个可滚动的结果集
listDistinct 若是子查询或者关联被使用,有一个可能就是在结果集中屡次出现同一行,这个方法容许只列出不一样的条目,它等价于 CriteriaSpecification 类的DISTINCT_ROOT_ENTITY
相关文章
相关标签/搜索