Grails 對象關聯映射 (GORM) 一


轉自:http://justjavac.iteye.com/blog/701445

 

 

 

Domain 類是任何商業應用的核心。 他們保存事務處理的狀態,也處理預期的行為。 他們通過關聯聯系在一起, one-to-one 或 one-to-many。

GORM 是 Grails對象關聯映射 (GORM)的實現。在底層,它使用 Hibernate 3 (一個非常流行和靈活的開源ORM解決方案),但是因為Groovy天生的動態性,實際上,對動態類型和靜態類型兩者都支持,由於Grails的規約,只需要很少的配置涉及Grails domain 類的創建。

你同樣可以在Java中編寫 Grails domain 類。 請參閱在 Hibernate 集成上如果在Java中編寫 Grails domain 類, 不過,它仍然使用動態持久方法。下面是GORM實戰預覽:

 

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

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

 

5.1 快速入門指南

domain類可以使用 create-domain-class 命令來創建:

 

grails create-domain-class Person

這將在 grails-app/domain/Person.groovy 位置上創建類,如下:

 

class Person {      
}

 

如果在   DataSource  上設置 dbCreate屬性為"update", "create" or "create-drop", Grails 會為你自動生成/修改數據表格。

你可以通過添加屬性來自定義類:

 

class Person {      
        String name
        Integer age
        Date lastVisit
}

一旦你擁有一個 domain 類,可以嘗試通過在 shell  console 上輸入:

 

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM