領先一步
VMware 提供培訓和認證,助您加速進步。
瞭解更多你是 Grails 新手嗎?或者你是否遇到了第一個 GORM 的“怪癖”?如果是這樣,那麼你一定要閱讀這個關於 GORM 陷阱的系列文章。這些文章不僅會揭示那些常常讓人們困惑的小特性,還會解釋 GORM 為什麼會表現出這些行為。
希望您已經知道 GORM 是 Grails 自帶的資料庫訪問庫。它基於可能是最流行的 Java ORM:Hibernate。可以想象,Hibernate 是一個強大且靈活的庫,它為 GORM 帶來了巨大的好處。但使用它是有代價的:GORM 使用者遇到的許多問題都源於 Hibernate 的工作方式。GORM 儘量隱藏其實現細節,但偶爾還是會暴露出來。
在本文的其餘部分,我將描述將物件持久化到資料庫的基礎知識。聽起來很簡單,但即使是這麼基本的操作,GORM 的行為也可能不像您預期的那樣。
儲存領域例項時遇到的問題可能是開發人員首先會遇到的。有多少人經歷過“我已經儲存了,為什麼它不在資料庫裡?”的階段?如果這對您有任何安慰的話,我也經歷過。為什麼會發生這種情況?有幾種可能性。
每次儲存領域例項時,Grails 都會使用您定義的約束來驗證它。如果領域例項中的任何值違反了這些約束,儲存就會失敗,並且約束錯誤會附加到領域例項上。麻煩的是,這種儲存領域例項的失敗是默默發生的:除非您檢查 return value ofsave()或呼叫hasErrors().
當您將使用者資料繫結到領域例項時,這通常是您想要的行為。如果使用者輸入碰巧不符合約束,這 hardly 是一個驚喜。在這種情況下丟擲異常是不合適的,特別是當您的 Web 應用程式有相當多的併發使用者時。在這種情況下,最好檢查 return value ofsave()並做出相應的反應(如果儲存失敗,它返回null,否則返回領域例項)
def book = new Book(params)
if (!book.save()) {
// Save failed! Present the errors to the user.
...
}
另一方面,當您在 BootStrap 或 Grails 控制檯中設定測試資料時,您通常期望儲存能夠成功。如果存在任何驗證錯誤,則意味著您自己犯了錯誤。在這種情況下,您不想費心檢查每個save()的 return value,如果您能讓 Grails 為驗證失敗丟擲異常,您會非常樂意。這不是預設行為,但您可以透過failOnError引數輕鬆開啟。
book.save(failOnError: true)
如果您堅持,您甚至可以將其設為預設值:只需在 grails-app/conf/Config.groovy 中將grails.gorm.failOnError轉換為設定為true即可。別忘了,所有領域屬性都隱式帶有nullable: false
約束!
Hibernate 的會話
偶爾,您可能會發現自己儲存了一個領域例項,卻發現隨後的查詢無法獲取它,即使該例項通過了驗證。這是使用 Hibernate 作為底層的原因所帶來的更廣泛問題的症狀。
Hibernate 是一個基於會話的 ORM 框架。
這一點非常重要,任何希望真正精通 GORM 的人都必須瞭解什麼是會話以及它對應用程式有什麼影響。那麼,什麼是會話?它基本上是後端是資料庫的物件的記憶體快取。當您儲存一個新的領域例項時,它會被隱式地附加到會話中,即新增到快取中,併成為一個 Hibernate 管理的物件。但此時它可能尚未持久化到資料庫! 下圖說明了這種行為
當您儲存例項時,它們將立即可從會話中獲取。但是 Hibernate 會自行決定何時將新例項持久化到資料庫,這意味著它可以最佳化 SQL 語句的順序。通常您不會注意到這些,因為 Grails 和 Hibernate 會處理好一切,但偶爾您會被 caught out。
book.save(flush: true)
的不過,正如您所期望的,Grails 允許您控制資料何時實際持久化到資料庫。您是否曾在示例中看到過這樣的程式碼?flush: true
強制 Hibernate 立即將所有更改持久化到資料庫。這對應於所謂的重新整理會話。不過,正如您所期望的,Grails 允許您控制資料何時實際持久化到資料庫。您是否曾在示例中看到過這樣的程式碼?現在的危險是,您可能會到處新增
。不要這樣做。讓 Hibernate 做它的工作,只在您必須手動重新整理會話時才這樣做,或者至少只在一批更新的末尾這樣做。只有當您在資料庫中看不到本應存在的資料時,才應該真正使用它。我知道這有點含糊,但這種操作的必要情況取決於資料庫實現和其他因素。手動重新整理在與另一個應用程式或正在訪問您的資料庫但不在您當前會話中的內部服務互動時非常有用。
現在我在您不希望我儲存的時候儲存了?!save()未能持久化領域例項(當您呼叫save()時)相當普遍。現在考慮相反的情況:在沒有相應呼叫
的情況下持久化物件。如果您還沒有遇到過這種行為,我幾乎可以保證您會遇到的。那麼為什麼可能會發生這種情況?BookHibernate 支援髒檢查的概念。這意味著 Hibernate 會檢查領域例項的(持久化)屬性的任何值是否在從資料庫中檢索出該例項後發生了變化,並會將這些更改持久化到資料庫。這有點拗口,希望一個例子能幫助您理解這個解釋。假設我們有一個domain class,其中有還是作者title
def b = Book.findByAuthor(params.author)
b.title = b.title.reverse()
屬性,並且控制器操作中有以下程式碼:save()請注意,這裡沒有呼叫
讓我們更詳細地看一下這些。首先,我之前提到過物件可以“附加”到會話,即由 Hibernate 管理並由資料庫支援,但物件是如何附加的呢?如果您以任何方式使用 GORM 檢索領域例項,例如透過get()方法或任何型別的查詢,那麼該物件會自動與會話關聯。如果您只是透過new關鍵字建立一個新例項,那麼該物件在呼叫save()方法之前不會附加到會話。
其次,領域類屬性預設是持久化的,即它們在資料庫中有匹配的列來儲存它們的值。您可以透過將它們的名稱新增到靜態transients列表屬性來將屬性設為 transient,這意味著它們的值不會儲存在資料庫中。
最後,我提到當會話關閉時發生的更改會被持久化。我的意思是什麼?為了透過 Hibernate 對資料庫進行任何操作,您必須有一個開啟的會話。一旦會話關閉,您就不能再使用它進行資料庫訪問了。此外,會話在關閉時會被重新整理,這就是為什麼上面的控制器操作結束時會持久化更改(Grails 會在請求開始時自動開啟會話,並在結束時關閉它)。
您能否阻止此類更改被持久化?當然。一種選擇是在領域例項上呼叫save():如果任何屬性的值驗證失敗,更改將不會被持久化。當然,如果值是有效的,但您仍然不想持久化它們,可以呼叫discard()在您的例項上。這不會重置例項屬性的值,但可以確保它們不會儲存到資料庫中。
這些只是幾個陷阱,資訊量相當大。關鍵在於理解 Hibernate 會話如何影響領域例項的持久化。即使您還沒有完全理解它,未來的 GORM 陷阱文章也會提供更多資訊和示例。
總的來說,我建議您始終使用save()來持久化物件,而不是依賴於髒檢查。它在程式碼中清楚地表明您希望持久化更改,並且這些更改會同時得到驗證。我也建議您始終檢查save()的 return value,儘管在設定載入程式或測試資料時,您最好使用failOnError: true選項。
如果您此時對 GORM 感到害怕或猶豫,請不要這樣。它確實讓與資料庫互動變得輕鬆有趣,並且透過這一系列文章獲得的知識,您將有信心處理可能出現的任何問題。
下次見!