国产一级一区二区_segui88久久综合9999_97久久夜色精品国产_欧美色网一区二区

掃一掃
關(guān)注微信公眾號

一種新的思維方式?NoSQL數(shù)據(jù)建模
2010-07-29   網(wǎng)絡(luò)

關(guān)系數(shù)據(jù)庫已經(jīng)統(tǒng)治數(shù)據(jù)存儲30 多年了,但是無模式(或NoSQL)數(shù)據(jù)庫的逐漸流行表明變化正在發(fā)生。盡管 RDBMS 為在傳統(tǒng)的客戶端服務(wù)器架構(gòu)中存儲數(shù)據(jù)提供了一個堅實的基礎(chǔ),但它不能輕松地(或便宜地)擴展到多個節(jié)點。在高度可伸縮的 Web 應(yīng)用程序(比如 Facebook 和 Twitter)的時代,這是一個非常不幸的弱點。

盡管關(guān)系數(shù)據(jù)庫的早期替代方案(還記得面向?qū)ο蟮臄?shù)據(jù)庫嗎?)不能解決真正緊急的問 題,NoSQL 數(shù)據(jù)庫(比如 Google 的 Bigtable 和 Amazon 的 SimpleDB)卻作為對 Web 的高可伸縮性需求的直接響應(yīng)而崛起。本質(zhì)上,NoSQL 可能是一個殺手問題的殺手應(yīng)用程序 —隨著 Web 2.0 的演變,Web 應(yīng)用程序開發(fā)人員可能會遇到更多,而不是更少這樣的應(yīng)用程序。

在這期 Java 開發(fā) 2.0 中,我將向您介紹無模式數(shù)據(jù)建模,這是經(jīng)過關(guān)系思維模式訓(xùn)練的許多開發(fā)人員使用 NoSQL 的主要障礙。您將了解到,從一個域模型(而不是關(guān)系模型)入手是簡化您的改變的關(guān)鍵。如果您使用 Bigtable(如我的示例所示),您可以借助 Gaelyk:Google App Engine 的一個輕量級框架擴展。

NoSQL:一種新的思維方式?

當(dāng)開發(fā)人員談?wù)摲顷P(guān)系或 NoSQL 數(shù)據(jù)庫時,經(jīng)常提到的第一件事是他們需要改變思維方式。我認為,那實際上取決于您的初始數(shù)據(jù)建模方法。如果您習(xí)慣通過首先建模數(shù)據(jù)庫結(jié)構(gòu)(即首先確定表及 其關(guān)聯(lián)關(guān)系)來設(shè)計應(yīng)用程序,那么使用一個無模式數(shù)據(jù)存儲(比如 Bigtable)來進行數(shù)據(jù)建模則需要您重新思考您的做事方式。但是,如果您從域模型開始設(shè)計您的應(yīng)用程序,那么 Bigtable 的無模式結(jié)構(gòu)將看起來更自然。

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

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

實體和關(guān)系

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

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

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

  1. def ticket = new Entity("ticket")
  2. ticket.officer = params.officer
  3. ticket.license = params.plate
  4. ticket.issuseDate = offensedate
  5. ticket.location = params.location
  6. ticket.notes = params.notes
  7. ticket.offense = params.offense

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

返回比賽

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

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

圖1. 比賽和參賽者

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

無模式數(shù)據(jù)存儲的好處之一是無須事先知道所有事情,也就是說,與使用關(guān)系數(shù)據(jù)庫架構(gòu)相比,可 以更輕松地適應(yīng)變化。(注意,我并非暗示不能更改架構(gòu);我只是說,可以更輕松地適應(yīng)變化。)我不打算定義我的域?qū)ο笊系膶傩?—我將其推遲到 Groovy 的動態(tài)特性(實際上,這個特性允許創(chuàng)建針對 Google 的 Entity對象的域?qū)ο蟠恚O喾矗覍盐业臅r間花費在確定如何查找對象并處理關(guān)系上。這是 NoSQL 和各種利用無模式數(shù)據(jù)存儲的框架還沒有內(nèi)置的功能。

Model 基類

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

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

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

  1. package com.b50.nosql
  2. import com.google.appengine.api.datastore.DatastoreServiceFactory
  3. import com.google.appengine.api.datastore.Entity
  4. abstract class Model {
  5. def entity
  6. static def datastore = DatastoreServiceFactory.datastoreService
  7. public Model(){
  8. super()
  9. }
  10. public Model(params){
  11. this.@entity = new Entity(this.getClass().simpleName)
  12. params.each{ key, val ->
  13. this.setProperty key, val
  14. }
  15. }
  16. def getProperty(String name) {
  17. if(name.equals("id")){
  18. return entity.key.id
  19. }else{
  20. return entity."${name}"
  21. }
  22. }
  23. void setProperty(String name, value) {
  24. entity."${name}" = value
  25. }
  26. def save(){
  27. this.entity.save()
  28. }
  29. }

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

檢查一下 setProperty方法就會發(fā)現(xiàn) “鍵” 設(shè)置為底層 entity的屬性名稱,而對應(yīng)的 “值” 是該 entity的值。

Groovy 技巧

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

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

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

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

Race 子類

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

清單3. 一個 Race 子類

  1. package com.b50.nosql
  2. class Race extends Model {
  3. public Race(params){
  4. super(params)
  5. }
  6. }

當(dāng)一個子類使用一列參數(shù)(即一個包含多個 “鍵 / 值” 對的 Map)實例化時,一個對應(yīng)的 entity將在內(nèi)存中創(chuàng)建。要持久存儲它,只需調(diào)用 save方法。

清單4. 創(chuàng)建一個 Race 實例并將其保存到 GAE 的數(shù)據(jù)存儲

  1. import com.b50.nosql.Runner
  2. def iparams = [:]
  3. def formatter = new SimpleDateFormat("MM/dd/yyyy")
  4. def rdate = formatter.parse("04/17/2010")
  5. iparams["name"] = "Charlottesville Marathon"
  6. iparams["date"] = rdate
  7. iparams["distance"] = 26.2 as double
  8. def race = new Race(iparams)
  9. race.save()

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

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

圖2. 查看新創(chuàng)建的Race

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

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

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

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

  1. static def findByName(name){
  2. def query = new Query(Race.class.simpleName)
  3. query.addFilter("name", Query.FilterOperator.EQUAL, name)
  4. def preparedQuery = this.datastore.prepare(query)
  5. if(preparedQuery.countEntities() > 1){
  6. return new Race(preparedQuery.asList(withLimit(1))[0])
  7. }else{
  8. return new Race(preparedQuery.asSingleEntity())
  9. }
  10. }

這個簡單的查找程序使用 Google App Engine 的 Query和 PreparedQuery類型來查找一個類型為 “Race” 的實體,其名稱(完全)等同于傳入的名稱。如果有超過一個 Race符合這個標(biāo)準(zhǔn),查找程序?qū)⒎祷匾粋€列表的第一項,這是分頁限制 1(withLimit(1))所指定的。

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

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

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

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

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

  1. def nrace = Race.findByName("Charlottesville Marathon")
  2. assert nrace.distance == 26.2
  3. def races = Race.findAllByName("Charlottesville Marathon")
  4. assert races.class == ArrayList.class

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

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

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

清單 8. 創(chuàng)建一個參賽者很簡單

  1. package com.b50.nosql
  2. class Runner extends Model{
  3. public Runner(params){
  4. super(params)
  5. }
  6. }

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

沒有架構(gòu)的域建模

Google App Engine 在 Bigtable 上面的抽象不是一個面向?qū)ο蟮某橄螅患矗也荒茉瓨哟鎯﹃P(guān)系,但可以共享鍵。因此,為建模多個 Race和多個 Runner之間的關(guān)系,我將在每個 Race實例中存儲一列 Runner鍵,并在每個 Runner實例中存儲一列 Race鍵。

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

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

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

  1. def addRunner(runner){
  2. if(this.@entity.runners){
  3. this.@entity.runners << runner.id
  4. }else{
  5. this.@entity.runners = [runner.id]
  6. }
  7. }
  8. def getRunners(){
  9. return this.@entity.runners.collect {
  10. new Runner( this.getEntity(Runner.class.simpleName, it) )
  11. }
  12. }

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

清單10. 從一個id 創(chuàng)建一個實體

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

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

最后,定義一個新的構(gòu)造函數(shù)來接受一個實體類型,如清單 11 所示:

清單11. 一個新添加的構(gòu)造函數(shù)

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

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

清單12. 參賽者及其比賽

  1. def addRace(race){
  2. if(this.@entity.races){
  3. this.@entity.races << race.id
  4. }else{
  5. this.@entity.races = [race.id]
  6. }
  7. }
  8. def getRaces(){
  9. return this.@entity.races.collect {
  10. new Race( this.getEntity(Race.class.simpleName, it) )
  11. }
  12. }

這樣,我就使用一個無模式數(shù)據(jù)存儲創(chuàng)建了兩個域?qū)ο蟆?/p>

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

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

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

  1. def runner = new Runner([fname:"Chris", lname:"Smith", date:34])
  2. runner.save()
  3. race.addRunner(runner)
  4. race.save()
  5. runner.addRace(race)
  6. runner.save()

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

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

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

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

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

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

但是,將一個 Race關(guān)聯(lián)到一個 Runner后,數(shù)據(jù)存儲將添加新的 races ids 的 list。

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

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

NoSQL 的利弊

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

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

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

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

結(jié)束語

流行時尚來了又去,有時無需理會它們(明智的建議來自一個衣櫥里滿是休閑服的家伙)。但 NoSQL 看起來不太像一種時尚,更像是高度可伸縮的 Web 應(yīng)用程序開發(fā)的一個新興基礎(chǔ)。NoSQL 數(shù)據(jù)庫不會替代 RDBMS,但是,它們將補充它。無數(shù)成功的工具和框架基于關(guān)系數(shù)據(jù)庫,RDBMSs 本身似乎沒有面臨過時的危險。

總之,NoSQL 數(shù)據(jù)庫的作用是向?qū)ο?mdash;—關(guān)系數(shù)據(jù)模型提供一個及時的替代方案。它們向我們展示,有些事情是可行的,并且對于一些特定的、高度強制的用例甚至更好。無模式 數(shù)據(jù)庫最適用于需要高速數(shù)據(jù)檢索和可伸縮性的多節(jié)點 Web 應(yīng)用程序。它們還有一個極好的副作用,即允許開發(fā)人員從一個面向域的視角、而不是關(guān)系視角進行數(shù)據(jù)建模。

熱詞搜索:

上一篇:開源非關(guān)系型數(shù)據(jù)庫Hibari云雀發(fā)布
下一篇:NoSQL生態(tài)系統(tǒng)大檢閱 不同特性大比拼

分享到: 收藏
主站蜘蛛池模板: 遂川县| 定兴县| 昌都县| 新津县| 正蓝旗| 碌曲县| 湘西| 杭州市| 蕉岭县| 镇远县| 萝北县| 金寨县| 宜昌市| 营口市| 修水县| 易门县| 增城市| 出国| 千阳县| 泗阳县| 娱乐| 老河口市| 方山县| 阳东县| 宁蒗| 四子王旗| 儋州市| 定边县| 内江市| 高邑县| 且末县| 漳平市| 文安县| 尉氏县| 铜山县| 成武县| 广东省| 剑阁县| 宜兰县| 奉节县| 南开区|