久久精品人人爽,华人av在线,亚洲性视频网站,欧美专区一二三

NoSQL數據建模的示例分析

174次閱讀
沒有評論

共計 8632 個字符,預計需要花費 22 分鐘才能閱讀完成。

這篇文章將為大家詳細講解有關 NoSQL 數據建模的示例分析,丸趣 TV 小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。

NoSQL:一種新的思維方式?

當開發人員談論非關系或 NoSQL 數據庫時,經常提到的第一件事是他們需要改變思維方式。我認為,那實際上取決于您的初始數據建模方法。如果您習慣通過首先建模數據庫結構(即首先確定表及其關聯關系)來設計應用程序,那么使用一個無模式數據存儲(比如 Bigtable)來進行數據建模則需要您重新思考您的做事方式。但是,如果您從域模型開始設計您的應用程序,那么 Bigtable 的無模式結構將看起來更自然。

非關系數據存儲沒有聯接表或主鍵,甚至沒有外鍵這個概念(盡管這兩種類型的鍵以一種更松散的形式出現)。因此,如果您嘗試將關系建模作為一個 NoSQL 數據庫中的數據建模的基礎,那么您可能最后以失敗告終。從域模型開始將使事情變得簡單;實際上,我已經發現,域模型下的無模式結構的靈活性正在重新煥發生機。

從關系數據模型遷移到無模式數據模型的相對復雜程度取決于您的方法:即您從基于關系的設計開始還是從基于域的設計開始。當您遷移到 CouchDB 或 Bigtable 這樣的數據庫時,您 的確會喪失 Hibernate(至少現在)這樣的成熟的持久存儲平臺的順暢感覺。另一方面,您卻擁有能夠親自構建它的“綠地效果”。在此過程中,您將深入了解無模式數據存儲。

實體和關系

無模式數據存儲賦予您首先使用對象來設計域模型的靈活性(Grails 這樣的較新的框架自動支持這種靈活性)。您的下一步工作是將您的域映射到底層數據存儲,這在使用 Google App Engine 時再簡單不過了。

在文章“Java 開發 2.0:針對 Google App Engine 的 Gaelyk”中,我介紹了 Gaelyk —— 一個基于 Groovy 的框架,該框架有利于使用 Google 的底層數據存儲。那篇文章的主要部分關注如何利用 Google 的 Entity 對象。下面的示例(來自那篇文章)將展示對象實體如何在 Gaelyk 中工作。

清單 1. 使用 Entity 的對象持久存儲

def ticket = new Entity(ticket) ticket.officer = params.officer ticket.license = params.plate ticket.issuseDate = offensedate ticket.location = params.location ticket.notes = params.notes ticket.offense = params.offense

這種對象持久存儲方法很有效,但容易看出,如果您頻繁使用票據實體 —例如,如果您正在各種 servlet 中創建(或查找)它們,那么這種方法將變得令人厭煩。使用一個公共 servlet(或 Groovlet)來為您處理這些任務將消除其中一些負擔。一種更自然的選擇——我將稍后展示——將是建模一個 Ticket 對象。

返回比賽

我不會重復 Gaelyk 簡介中的那個票據示例,相反,為保持新鮮感,我將在本文中使用一個賽跑主題,并構建一個應用程序來展示即將討論的技術。

如圖 1 中的“多對多”圖表所示,一個 Race 擁有多個 Runner,一個 Runner 可以屬于多個 Race。

圖 1. 比賽和參賽者

如果我要使用一個關系表結構來設計這個關系,至少需要 3 個表:第 3 表將是鏈接一個“多對多”關系的聯接表。所幸我不必局限于關系數據模型。相反,我將使用 Gaelyk(和 Groovy 代碼)將這個“多對多”關系映射到 Google 針對 Google App Engine 的 Bigtable 抽象。事實上,Gaelyk 允許將 Entity 當作 Map,這使得映射過程相當簡單。

無模式數據存儲的好處之一是無須事先知道所有事情,也就是說,與使用關系數據庫架構相比,可以更輕松地適應變化。(注意,我并非暗示不能更改架構;我只是說,可以更輕松地適應變化。)我不打算定義我的域對象上的屬性 —我將其推遲到 Groovy 的動態特性(實際上,這個特性允許創建針對 Google 的 Entity 對象的域對象代理)。相反,我將把我的時間花費在確定如何查找對象并處理關系上。這是 NoSQL 和各種利用無模式數據存儲的框架還沒有內置的功能。

Model 基類

我將首先創建一個基類,用于容納 Entity 對象的一個實例。然后,我將允許一些子類擁有一些動態屬性,這些動態屬性將通過 Groovy 的方便的 setProperty 方法添加到對應的 Entity 實例。setProperty 針對對象中實際上不存在的任何屬性設置程序調用。(如果這聽起來聳人聽聞,不用擔心,您看到它的實際運行后就會明白。)

清單 2 展示了位于我的示例應用程序的一個 Model 實例的第一個 stab:

清單 2. 一個簡單的 Model 基類

package com.b50.nosql import com.google.appengine.api.datastore.DatastoreServiceFactory import com.google.appengine.api.datastore.Entity abstract class Model { def entity static def datastore = DatastoreServiceFactory.datastoreService public Model(){ super() } public Model(params){ this.@entity = new Entity(this.getClass().simpleName) params.each{ key, val -  this.setProperty key, val } } def getProperty(String name) { if(name.equals( id)){ return entity.key.id }else{ return entity. ${name}  } } void setProperty(String name, value) { entity. ${name}  = value } def save(){ this.entity.save() } }

注意抽象類如何定義一個構造函數,該函數接收屬性的一個 Map ——我總是可以稍后添加更多構造函數,稍后我就會這么做。這個設置對于 Web 框架十分方便,這些框架通常采用從表單提交的參數。Gaelyk 和 Grails 將這樣的參數巧妙地封裝到一個稱為 params 的對象中。這個構造函數迭代這個 Map 并針對每個“鍵 / 值”對調用 setProperty 方法。

檢查一下 setProperty 方法就會發現“鍵”設置為底層 entity 的屬性名稱,而對應的“值”是該 entity 的值。

Groovy 技巧

如前所述,Groovy 的動態特性允許我通過 get 和 set Property 方法捕獲對不存在的屬性的方法調用。這樣,清單 2 中的 Model 的子類不必定義它們自己的屬性 —它們只是將對一個屬性的所有調用委托給這個底層 entity 對象。

清單 2 中的代碼執行了一些特定于 Groovy 的操作,值得一提。首先,可以通過在一個屬性前面附加一個 @來繞過該屬性的訪問器方法。我必須對構造函數中的 entity 對象引用執行上述操作,否則我將調用 setProperty 方法。很明顯,在這個關頭調用 setProperty 將打破這種模式,因為 setProperty 方法中的 entity 變量將是 null。

其次,構造函數中的調用 this.getClass().simpleName 將設置 entity 的“種類”—— simpleName 屬性將生成一個不帶包前綴的子類名稱(注意,simpleName 的確是對 getSimpleName 的調用,但 Groovy 允許我不通過對應的 JavaBeans 式的方法調用來嘗試訪問一個屬性)。

最后,如果對 id 屬性(即,對象的鍵)進行一個調用,getProperty 方法很智能,能夠詢問底層 key 以獲取它的 id。在 Google App Engine 中,entities 的 key 屬性將自動生成。

Race 子類

定義 Race 子類很簡單,如清單 3 所示:

清單 3. 一個 Race 子類

package com.b50.nosql class Race extends Model { public Race(params){ super(params) } }

當一個子類使用一列參數(即一個包含多個“鍵 / 值”對的 Map)實例化時,一個對應的 entity 將在內存中創建。要持久存儲它,只需調用 save 方法。

清單 4. 創建一個 Race 實例并將其保存到 GAE 的數據存儲

import com.b50.nosql.Runner def iparams = [:] def formatter = new SimpleDateFormat(MM/dd/yyyy) def rdate = formatter.parse(04/17/2010) iparams[name] =  Charlottesville Marathon  iparams[date] = rdate iparams[distance] = 26.2 as double def race = new Race(iparams) race.save()

清單 4 是一個 Groovlet,其中,一個 Map(稱為 iparams)創建為帶有 3 個屬性 ——一次比賽的名稱、日期和距離。(注意,在 Groovy 中,一個空白 Map 通過 [:] 創建。)Race 的一個新實例被創建,然后通過 save 方法存儲到底層數據存儲。

可以通過 Google App Engine 控制臺來查看底層數據存儲,確保我的數據的確在那里,如圖 2 所示:

圖 2. 查看新創建的 Race

查找程序方法生成持久存儲的實體

現在我已經存儲了一個 Entity,擁有查找它的能力將有所幫助。接下來,我可以添加一個“查找程序”方法。在本例中,我將把這個“查找程序”方法創建為一個類方法(static)并且允許通過名稱查找這些 Race(即基于 name 屬性搜索)。稍后,總是可以通過其他屬性添加其他查找程序。

我還打算對我的查找程序采用一個慣例,即指定:任何名稱中不帶單詞 all 的查找程序都企圖找到 一個實例。名稱中包含單詞 all 的查找程序(如 findAllByName)能夠返回一個實例 Collection 或 List。清單 5 展示了 findByName 查找程序:

清單 5. 一個基于 Entity 名稱搜索的簡單查找程序

static def findByName(name){ def query = new Query(Race.class.simpleName) query.addFilter(name , Query.FilterOperator.EQUAL, name) def preparedQuery = this.datastore.prepare(query) if(preparedQuery.countEntities()   1){ return new Race(preparedQuery.asList(withLimit(1))[0]) }else{ return new Race(preparedQuery.asSingleEntity()) } }

這個簡單的查找程序使用 Google App Engine 的 Query 和 PreparedQuery 類型來查找一個類型為“Race”的實體,其名稱(完全)等同于傳入的名稱。如果有超過一個 Race 符合這個標準,查找程序將返回一個列表的第一項,這是分頁限制 1(withLimit(1))所指定的。

對應的 findAllByName 與上述方法類似,但添加了一個參數,指定 您想要的實體個數,如清單 6 所示:

清單 6. 通過名稱找到全部實體

static def findAllByName(name, pagination=10){ def query = new Query(Race.class.getSimpleName()) query.addFilter(name , Query.FilterOperator.EQUAL, name) def preparedQuery = this.datastore.prepare(query) def entities = preparedQuery.asList(withLimit(pagination as int)) return entities.collect { new Race(it as Entity) } }

與前面定義的查找程序類似,findAllByName 通過名稱找到 Race 實例,但是它返回 所有 Race。順便說一下,Groovy 的 collect 方法非常靈活:它允許刪除創建 Race 實例的對應的循環。注意,Groovy 還支持方法參數的默認值;這樣,如果我沒有傳入第 2 個值,pagination 將擁有值 10。

清單 7. 查找程序的實際運行

def nrace = Race.findByName(Charlottesville Marathon) assert nrace.distance == 26.2 def races = Race.findAllByName(Charlottesville Marathon) assert races.class == ArrayList.class

清單 7 中的查找程序按照既定的方式運行:findByName 返回一個實例,而 findAllByName 返回一個 Collection(假定有多個“Charlottesville Marathon”)。

“參賽者”對象沒有太多不同

現在我已能夠創建并找到 Race 的實例,現在可以創建一個快速的 Runner 對象了。這個過程與創建初始的 Race 實例一樣簡單,只需如清單 8 所示擴展 Model:

清單 8. 創建一個參賽者很簡單

package com.b50.nosql class Runner extends Model{ public Runner(params){ super(params) } }

看看 清單 8,我感覺自己幾乎完成工作了。但是,我還需創建參賽者和比賽之間的鏈接。當然,我將把它建模為一個“多對多”關系,因為我希望我的參賽者可以參加多項比賽。

沒有架構的域建模

Google App Engine 在 Bigtable 上面的抽象不是一個面向對象的抽象;即,我不能原樣存儲關系,但可以共享鍵。因此,為建模多個 Race 和多個 Runner 之間的關系,我將在每個 Race 實例中存儲一列 Runner 鍵,并在每個 Runner 實例中存儲一列 Race 鍵。

我必須對我的鍵共享機制添加一點邏輯,但是,因為我希望生成的 API 比較自然 —我不想詢問一個 Race 以獲取一列 Runner 鍵,因此我想要一列 Runner。幸運的是,這并不難實現。

在清單 9 中,我已經添加了兩個方法到 Race 實例。但一個 Runner 實例被傳遞到 addRunner 方法時,它的對應 id 被添加到底層 entity 的 runners 屬性中駐留的 id 的 Collection。如果有一個現成的 runners 的 collection,則新的 Runner 實例鍵將添加到它;否則,將創建一個新的 Collection,且這個 Runner 的鍵(實體上的 id 屬性)將添加到它。

清單 9. 添加并檢索參賽者

def addRunner(runner){ if(this.@entity.runners){ this.@entity.runners   runner.id }else{ this.@entity.runners = [runner.id] } } def getRunners(){ return this.@entity.runners.collect { new Runner( this.getEntity(Runner.class.simpleName, it) ) } }

當清單 9 中的 getRunners 方法調用時,一個 Runner 實例集合將從底層的 id 集合創建。這樣,一個新方法(getEntity)將在 Model 類中創建,如清單 10 所示:

清單 10. 從一個 id 創建一個實體

def getEntity(entityType, id){ def key = KeyFactory.createKey(entityType, id) return this.@datastore.get(key) }

getEntity 方法使用 Google 的 KeyFactory 類來創建底層鍵,它可以用于查找數據存儲中的一個單獨實體。

最后,定義一個新的構造函數來接受一個實體類型,如清單 11 所示:

清單 11. 一個新添加的構造函數

public Model(Entity entity){ this.@entity = entity }

如清單 9、10 和 11、以及 圖 1 的對象模型所示,我可以將一個 Runner 添加到任一 Race,也可以從任一 Race 獲取一列 Runner 實例。在清單 12 中,我在這個等式的 Runner 方上創建了一個類似的聯系。清單 12 展示了 Runner 類的新方法。

清單 12. 參賽者及其比賽

def addRace(race){ if(this.@entity.races){ this.@entity.races   race.id }else{ this.@entity.races = [race.id] } } def getRaces(){ return this.@entity.races.collect { new Race( this.getEntity(Race.class.simpleName, it) ) } }

這樣,我就使用一個無模式數據存儲創建了兩個域對象。

通過一些參賽者完成這個比賽

此前我所做的是創建一個 Runner 實例并將其添加到一個 Race。如果我希望這個關系是雙向的,如圖 1 中我的對象模型所示,那么我也可以添加一些 Race 實例到一些 Runner,如清單 13 所示:

清單 13. 參加多個比賽的多個參賽者

def runner = new Runner([fname: Chris , lname: Smith , date:34]) runner.save() race.addRunner(runner) race.save() runner.addRace(race) runner.save()

將一個新的 Runner 添加到 race 并添加對 Race 的 save 的調用后,這個數據存儲已使用一列 ID 更新,如圖 3 中的屏幕快照所示:

圖 3. 查看一項比賽中的多個參賽者的新屬性

通過仔細檢查 Google App Engine 中的數據,可以看到,一個 Race 實體現在擁有了一個 Runners 的 list,如圖 4 所示。

圖 4. 查看新的參賽者列表

同樣,在將一個 Race 添加到一個新創建的 Runner 實例之前,這個屬性并不存在,如圖 5 所示。

圖 5. 一個沒有比賽的參賽者

但是,將一個 Race 關聯到一個 Runner 后,數據存儲將添加新的 races ids 的 list。

圖 6. 一個參加比賽的參賽者

無模式數據存儲的靈活性正在刷新 —屬性按照需要自動添加到底層存儲。作為開發人員,我無須更新或更改架構,更談不上部署架構了!

NoSQL 的利弊

當然,無模式數據建模也有利有弊。回顧上面的比賽應用程序,它的一個優勢是非常靈活。如果我決定將一個新屬性(比如 SSN)添加到一個 Runner,我不必進行大幅更改 —事實上,如果我將該屬性包含在構造函數的參數中,那么它就會自動添加。對那些沒有使用一個 SSN 創建的舊實例而言,發生了什么事情?什么也沒發生!它們擁有一個值為 null 的字段。

另一方面,我已經明確表明要犧牲一致性和完整性來換取效率。這個應用程序的當前數據架構沒有向我施加任何限制 —理論上我可以為同一個對象創建無限個實例。在 Google App Engine 引擎的鍵處理機制下,它們都有惟一的鍵,但其他屬性都是一致的。更糟糕的是,級聯刪除不存在,因此如果我使用相同的技術來建模一個“一對多”關系并刪除父節點,那么我得到一些無效的子節點。當然,我可以實現自己的完整性檢查 —但關鍵是,我必須親自動手(就像完成其他任務一樣)。

使用無模式數據存儲需要嚴明的紀律。如果我創建各種類型的 Races —有些有名稱,有些沒有,有些有 date 屬性,而另一些有 race_date 屬性 —那么我只是在搬起石頭砸自己(或使用我的代碼的人)的腳。

當然,也有可能聯合使用 JDO、JPA 和 Google App Engine。在多個項目上使用過關系模型和無模式模型后,我可以說 Gaelyk 的低級 API 最靈活,使用最方便。使用 Gaelyk 的另一個好處是能夠深入了解 Bigtable 和一般的無模式數據存儲。

關于“NoSQL 數據建模的示例分析”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。

正文完
 
丸趣
版權聲明:本站原創文章,由 丸趣 2023-07-15發表,共計8632字。
轉載說明:除特殊說明外本站除技術相關以外文章皆由網絡搜集發布,轉載請注明出處。
評論(沒有評論)
主站蜘蛛池模板: 伊金霍洛旗| 永吉县| 长沙市| 咸宁市| 新田县| 泸水县| 塔河县| 休宁县| 荥阳市| 临清市| 即墨市| 湖口县| 南宁市| 石棉县| 方正县| 贺兰县| 连州市| 繁峙县| 宽城| 土默特右旗| 常山县| 道真| 建瓯市| 临沧市| 清涧县| 泰安市| 绵竹市| 呼图壁县| 郑州市| 安塞县| 巩留县| 根河市| 阳泉市| 金溪县| 上栗县| 武清区| 酒泉市| 宜黄县| 永寿县| 蒲江县| 富裕县|