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
相關文章
相關標籤/搜索