轉自: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
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命令。安全
嘗試執行一些基礎的 CRUD (Create/Read/Update/Delete) 操做。
爲了建立一個 domain 類,可使用 Groovy new操做符, 設置它的屬性並調用 save:
def p = new Person(name:"Fred", age:40, lastVisit:new Date()) p.save()
save 方法將使用底層的Hibernate ORM持久你的類到數據庫中。
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 狀態.
更新一個實體, 設置一些屬性,而後,只需再次調用 save:
def p = Person.get(1)
p.name = "Bob"
p.save()
刪除一個實體使用 delete 方法:
def p = Person.get(1) p.delete()
當構建 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定製。
關聯定義了domain類之間的相互做用。除非在兩端明確的指定,不然關聯只存在被定義的一方。
one-to-one 關聯是最簡單的種類,它只是把它的一個屬性的類型定義爲其餘domain類。 考慮下面的例子:
class Face { Nose nose } class Nose { }
在這種狀況下, 擁有一個Face
到 Nose
的one-to-one單向關聯。爲了使它雙向關聯,須要定義另外一端,以下:
class Face { Nose nose } class Nose { Face face }
這就是雙向關聯。不過, 在這種狀況下,關聯的雙方並不能級聯更新。
考慮下這樣的變化:
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個示例:
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 問題 。
默認的級聯行爲是級聯保存和更新,但不刪除,除非 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 }
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關聯, 你必須本身編寫關聯的管理代碼
除了關聯不一樣 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 | ---------------------------------------------
除了 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
類。
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
默認狀況下,在中 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排序。
若是你只是想保持對象的順序,添加它們和引用它們經過索引,就像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()
若是你想要一個簡單的 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()
關於Grails要記住的很重要的一點就是,Grails的底層使用 Hibernate 來進行持久化. 若是您之前使用的是ActiveRecord 或者 iBatis 您可能會對Hibernate的"session"模型感到有點陌生.
本質上,Grails自動綁定Hibernate session到當前正在執行的請求上.這容許你像使用GORM的其餘方法同樣很天然地使用 save 和 delete 方法.
下面看一個使用 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 }
下面是 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"])
在使用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.
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".
class A { static hasMany = [bees:B] }
class B { A a }
若是是在多的一端沒有設置belongsTo
的雙向一對多關聯,那麼級聯策略將爲一的一端設置爲"SAVE-UPDATE" 爲多的一端設置爲"NONE".
class A { }
class B { static belongsTo = [a:A] }
若是是設置了belongsTo
的單向一對一關聯,那麼級聯策略將爲有關聯的一端(A->B)設置爲"ALL",定義了belongsTo
的一端(B->A)設置爲"NONE".
請注意,若是您須要進一步的控制級聯的行爲,您能夠參見 ORM DSL.
在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
雖然當即加載適合某些狀況,它並不老是可取的,若是您全部操做都使用當即加載,那麼您會將整個數據庫加載到內存中,致使性能和內存的問題.替代當即加載是使用批量加載.實際上,您能夠在"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
}
}
默認的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
}
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)
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()
方法表達式也可使用一個布爾操做符來組合兩個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"])
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") }
如前面例子所演示的,你能夠用 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天內新建立的全部用戶.
投影被用於定製查詢結果. 要使用投影你須要在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開始.
若是在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 |