關(guān)系數(shù)據(jù)庫(kù)已經(jīng)統(tǒng)治數(shù)據(jù)存儲(chǔ)30 多年了,但是無模式(或NoSQL)數(shù)據(jù)庫(kù)的逐漸流行表明變化正在發(fā)生。盡管 RDBMS 為在傳統(tǒng)的客戶端服務(wù)器架構(gòu)中存儲(chǔ)數(shù)據(jù)提供了一個(gè)堅(jiān)實(shí)的基礎(chǔ),但它不能輕松地(或便宜地)擴(kuò)展到多個(gè)節(jié)點(diǎn)。在高度可伸縮的 Web 應(yīng)用程序(比如 Facebook 和 Twitter)的時(shí)代,這是一個(gè)非常不幸的弱點(diǎn)。
盡管關(guān)系數(shù)據(jù)庫(kù)的早期替代方案(還記得面向?qū)ο蟮臄?shù)據(jù)庫(kù)嗎?)不能解決真正緊急的問 題,NoSQL 數(shù)據(jù)庫(kù)(比如 Google 的 Bigtable 和 Amazon 的 SimpleDB)卻作為對(duì) Web 的高可伸縮性需求的直接響應(yīng)而崛起。本質(zhì)上,NoSQL 可能是一個(gè)殺手問題的殺手應(yīng)用程序 —隨著 Web 2.0 的演變,Web 應(yīng)用程序開發(fā)人員可能會(huì)遇到更多,而不是更少這樣的應(yīng)用程序。
在這期 Java 開發(fā) 2.0 中,我將向您介紹無模式數(shù)據(jù)建模,這是經(jīng)過關(guān)系思維模式訓(xùn)練的許多開發(fā)人員使用 NoSQL 的主要障礙。您將了解到,從一個(gè)域模型(而不是關(guān)系模型)入手是簡(jiǎn)化您的改變的關(guān)鍵。如果您使用 Bigtable(如我的示例所示),您可以借助 Gaelyk:Google App Engine 的一個(gè)輕量級(jí)框架擴(kuò)展。
NoSQL:一種新的思維方式?
當(dāng)開發(fā)人員談?wù)摲顷P(guān)系或 NoSQL 數(shù)據(jù)庫(kù)時(shí),經(jīng)常提到的第一件事是他們需要改變思維方式。我認(rèn)為,那實(shí)際上取決于您的初始數(shù)據(jù)建模方法。如果您習(xí)慣通過首先建模數(shù)據(jù)庫(kù)結(jié)構(gòu)(即首先確定表及 其關(guān)聯(lián)關(guān)系)來設(shè)計(jì)應(yīng)用程序,那么使用一個(gè)無模式數(shù)據(jù)存儲(chǔ)(比如 Bigtable)來進(jìn)行數(shù)據(jù)建模則需要您重新思考您的做事方式。但是,如果您從域模型開始設(shè)計(jì)您的應(yīng)用程序,那么 Bigtable 的無模式結(jié)構(gòu)將看起來更自然。
非關(guān)系數(shù)據(jù)存儲(chǔ)沒有聯(lián)接表或主鍵,甚至沒有外鍵這個(gè)概念(盡管這兩種類型的鍵以一種更松散的 形式出現(xiàn))。因此,如果您嘗試將關(guān)系建模作為一個(gè) NoSQL 數(shù)據(jù)庫(kù)中的數(shù)據(jù)建模的基礎(chǔ),那么您可能最后以失敗告終。從域模型開始將使事情變得簡(jiǎn)單;實(shí)際上,我已經(jīng)發(fā)現(xiàn),域模型下的無模式結(jié)構(gòu)的靈活性正在重新煥發(fā)生 機(jī)。
從關(guān)系數(shù)據(jù)模型遷移到無模式數(shù)據(jù)模型的相對(duì)復(fù)雜程度取決于您的方法:即您從基于關(guān)系的設(shè)計(jì)開 始還是從基于域的設(shè)計(jì)開始。當(dāng)您遷移到 CouchDB 或 Bigtable 這樣的數(shù)據(jù)庫(kù)時(shí),您 的確會(huì)喪失 Hibernate(至少現(xiàn)在)這樣的成熟的持久存儲(chǔ)平臺(tái)的順暢感覺。另一方面,您卻擁有能夠親自構(gòu)建它的 “綠地效果”。在此過程中,您將深入了解無模式數(shù)據(jù)存儲(chǔ)。
實(shí)體和關(guān)系
無模式數(shù)據(jù)存儲(chǔ)賦予您首先使用對(duì)象來設(shè)計(jì)域模型的靈活性(Grails 這樣的較新的框架自動(dòng)支持這種靈活性)。您的下一步工作是將您的域映射到底層數(shù)據(jù)存儲(chǔ),這在使用 Google App Engine 時(shí)再簡(jiǎn)單不過了。
在文章 “Java 開發(fā) 2.0:針對(duì) Google App Engine 的 Gaelyk” 中,我介紹了 Gaelyk —— 一個(gè)基于 Groovy 的框架,該框架有利于使用 Google 的底層數(shù)據(jù)存儲(chǔ)。那篇文章的主要部分關(guān)注如何利用 Google 的 Entity對(duì)象。下面的示例(來自那篇文章)將展示對(duì)象實(shí)體如何在 Gaelyk 中工作。
清單1. 使用 Entity 的對(duì)象持久存儲(chǔ)
- 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
這種對(duì)象持久存儲(chǔ)方法很有效,但容易看出,如果您頻繁使用票據(jù)實(shí)體 —例如,如果您正在各種 servlet 中創(chuàng)建(或查找)它們,那么這種方法將變得令人厭煩。使用一個(gè)公共 servlet(或 Groovlet)來為您處理這些任務(wù)將消除其中一些負(fù)擔(dān)。一種更自然的選擇——我將稍后展示——將是建模一個(gè) Ticket對(duì)象。
返回比賽
我不會(huì)重復(fù) Gaelyk 簡(jiǎn)介中的那個(gè)票據(jù)示例,相反,為保持新鮮感,我將在本文中使用一個(gè)賽跑主題,并構(gòu)建一個(gè)應(yīng)用程序來展示即將討論的技術(shù)。
如圖 1 中的 “多對(duì)多” 圖表所示,一個(gè) Race擁有多個(gè) Runner,一個(gè) Runner可以屬于多個(gè) Race。
圖1. 比賽和參賽者
如果我要使用一個(gè)關(guān)系表結(jié)構(gòu)來設(shè)計(jì)這個(gè)關(guān)系,至少需要 3 個(gè)表:第 3 表將是鏈接一個(gè) “多對(duì)多” 關(guān)系的聯(lián)接表。所幸我不必局限于關(guān)系數(shù)據(jù)模型。相反,我將使用 Gaelyk(和 Groovy 代碼)將這個(gè) “多對(duì)多” 關(guān)系映射到 Google 針對(duì) Google App Engine 的 Bigtable 抽象。事實(shí)上,Gaelyk 允許將 Entity當(dāng)作 Map,這使得映射過程相當(dāng)簡(jiǎn)單。
無模式數(shù)據(jù)存儲(chǔ)的好處之一是無須事先知道所有事情,也就是說,與使用關(guān)系數(shù)據(jù)庫(kù)架構(gòu)相比,可 以更輕松地適應(yīng)變化。(注意,我并非暗示不能更改架構(gòu);我只是說,可以更輕松地適應(yīng)變化。)我不打算定義我的域?qū)ο笊系膶傩?—我將其推遲到 Groovy 的動(dòng)態(tài)特性(實(shí)際上,這個(gè)特性允許創(chuàng)建針對(duì) Google 的 Entity對(duì)象的域?qū)ο蟠恚O喾矗覍盐业臅r(shí)間花費(fèi)在確定如何查找對(duì)象并處理關(guān)系上。這是 NoSQL 和各種利用無模式數(shù)據(jù)存儲(chǔ)的框架還沒有內(nèi)置的功能。
Model 基類
我將首先創(chuàng)建一個(gè)基類,用于容納 Entity對(duì)象的一個(gè)實(shí)例。然后,我將允許一些子類擁有一些動(dòng)態(tài)屬性,這些動(dòng)態(tài)屬性將通過 Groovy 的方便的 setProperty方法添加到對(duì)應(yīng)的 Entity實(shí)例。setProperty針對(duì)對(duì)象中實(shí)際上不存在的任何屬性設(shè)置程序調(diào)用。(如果這聽起來聳人聽聞,不用擔(dān)心,您看到它的實(shí)際運(yùn)行后就會(huì) 明白。)
清單2展示了位于我的示例應(yīng)用程序的一個(gè) Model實(shí)例的第一個(gè) stab:
清單2. 一個(gè)簡(jiǎn)單的 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()
- }
- }
注意抽象類如何定義一個(gè)構(gòu)造函數(shù),該函數(shù)接收屬性的一個(gè) Map ——我總是可以稍后添加更多構(gòu)造函數(shù),稍后我就會(huì)這么做。這個(gè)設(shè)置對(duì)于 Web 框架十分方便,這些框架通常采用從表單提交的參數(shù)。Gaelyk 和 Grails 將這樣的參數(shù)巧妙地封裝到一個(gè)稱為 params的對(duì)象中。這個(gè)構(gòu)造函數(shù)迭代這個(gè) Map并針對(duì)每個(gè) “鍵 / 值” 對(duì)調(diào)用 setProperty方法。
檢查一下 setProperty方法就會(huì)發(fā)現(xiàn) “鍵” 設(shè)置為底層 entity的屬性名稱,而對(duì)應(yīng)的 “值” 是該 entity的值。
Groovy 技巧
如前所述,Groovy 的動(dòng)態(tài)特性允許我通過 get和 set Property方法捕獲對(duì)不存在的屬性的方法調(diào)用。這樣,清單 2 中的 Model的子類不必定義它們自己的屬性 —它們只是將對(duì)一個(gè)屬性的所有調(diào)用委托給這個(gè)底層 entity對(duì)象。
清單 2 中的代碼執(zhí)行了一些特定于 Groovy 的操作,值得一提。首先,可以通過在一個(gè)屬性前面附加一個(gè) @來繞過該屬性的訪問器方法。我必須對(duì)構(gòu)造函數(shù)中的 entity對(duì)象引用執(zhí)行上述操作,否則我將調(diào)用 setProperty方法。很明顯,在這個(gè)關(guān)頭調(diào)用 setProperty將打破這種模式,因?yàn)? setProperty方法中的 entity變量將是 null。
其次,構(gòu)造函數(shù)中的調(diào)用 this.getClass().simpleName將設(shè)置 entity的 “種類” —— simpleName屬性將生成一個(gè)不帶包前綴的子類名稱(注意,simpleName的確是對(duì) getSimpleName的調(diào)用,但 Groovy 允許我不通過對(duì)應(yīng)的 JavaBeans 式的方法調(diào)用來嘗試訪問一個(gè)屬性)。
最后,如果對(duì) id屬性(即,對(duì)象的鍵)進(jìn)行一個(gè)調(diào)用,getProperty方法很智能,能夠詢問底層 key以獲取它的 id。在 Google App Engine 中,entities的 key屬性將自動(dòng)生成。
Race 子類
定義 Race子類很簡(jiǎn)單,如清單 3 所示:
清單3. 一個(gè) Race 子類
- package com.b50.nosql
- class Race extends Model {
- public Race(params){
- super(params)
- }
- }
當(dāng)一個(gè)子類使用一列參數(shù)(即一個(gè)包含多個(gè) “鍵 / 值” 對(duì)的 Map)實(shí)例化時(shí),一個(gè)對(duì)應(yīng)的 entity將在內(nèi)存中創(chuàng)建。要持久存儲(chǔ)它,只需調(diào)用 save方法。
清單4. 創(chuàng)建一個(gè) Race 實(shí)例并將其保存到 GAE 的數(shù)據(jù)存儲(chǔ)
- 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 是一個(gè) Groovlet,其中,一個(gè) Map(稱為 iparams)創(chuàng)建為帶有 3 個(gè)屬性 ——一次比賽的名稱、日期和距離。(注意,在 Groovy 中,一個(gè)空白 Map通過 [:]創(chuàng)建。)Race的一個(gè)新實(shí)例被創(chuàng)建,然后通過 save方法存儲(chǔ)到底層數(shù)據(jù)存儲(chǔ)。
可以通過 Google App Engine 控制臺(tái)來查看底層數(shù)據(jù)存儲(chǔ),確保我的數(shù)據(jù)的確在那里,如圖 2 所示:
圖2. 查看新創(chuàng)建的Race
查找程序方法生成持久存儲(chǔ)的實(shí)體
現(xiàn)在我已經(jīng)存儲(chǔ)了一個(gè) Entity,擁有查找它的能力將有所幫助。接下來,我可以添加一個(gè) “查找程序” 方法。在本例中,我將把這個(gè) “查找程序” 方法創(chuàng)建為一個(gè)類方法(static)并且允許通過名稱查找這些 Race(即基于 name屬性搜索)。稍后,總是可以通過其他屬性添加其他查找程序。
我還打算對(duì)我的查找程序采用一個(gè)慣例,即指定:任何名稱中不帶單詞 all的查找程序都企圖找到 一個(gè)實(shí)例。名稱中包含單詞 all的查找程序(如 findAllByName)能夠返回一個(gè)實(shí)例 Collection或 List。清單 5 展示了 findByName查找程序:
清單5. 一個(gè)基于 Entity 名稱搜索的簡(jiǎn)單查找程序
- 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())
- }
- }
這個(gè)簡(jiǎn)單的查找程序使用 Google App Engine 的 Query和 PreparedQuery類型來查找一個(gè)類型為 “Race” 的實(shí)體,其名稱(完全)等同于傳入的名稱。如果有超過一個(gè) Race符合這個(gè)標(biāo)準(zhǔn),查找程序?qū)⒎祷匾粋€(gè)列表的第一項(xiàng),這是分頁限制 1(withLimit(1))所指定的。
對(duì)應(yīng)的 findAllByName與上述方法類似,但添加了一個(gè)參數(shù),指定 您想要的實(shí)體個(gè)數(shù),如清單 6 所示:
清單 6. 通過名稱找到全部實(shí)體
- 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實(shí)例,但是它返回 所有 Race。順便說一下,Groovy 的 collect方法非常靈活:它允許刪除創(chuàng)建 Race實(shí)例的對(duì)應(yīng)的循環(huán)。注意,Groovy 還支持方法參數(shù)的默認(rèn)值;這樣,如果我沒有傳入第 2 個(gè)值,pagination將擁有值 10。
清單7. 查找程序的實(shí)際運(yùn)行
- def nrace = Race.findByName("Charlottesville Marathon")
- assert nrace.distance == 26.2
- def races = Race.findAllByName("Charlottesville Marathon")
- assert races.class == ArrayList.class
清單 7中的查找程序按照既定的方式運(yùn)行:findByName返回一個(gè)實(shí)例,而 findAllByName返回一個(gè) Collection(假定有多個(gè) “Charlottesville Marathon”)。
“參賽者” 對(duì)象沒有太多不同
現(xiàn)在我已能夠創(chuàng)建并找到 Race的實(shí)例,現(xiàn)在可以創(chuàng)建一個(gè)快速的 Runner對(duì)象了。這個(gè)過程與創(chuàng)建初始的 Race實(shí)例一樣簡(jiǎn)單,只需如清單 8 所示擴(kuò)展 Model:
清單 8. 創(chuàng)建一個(gè)參賽者很簡(jiǎn)單
- package com.b50.nosql
- class Runner extends Model{
- public Runner(params){
- super(params)
- }
- }
看看 清單 8,我感覺自己幾乎完成工作了。但是,我還需創(chuàng)建參賽者和比賽之間的鏈接。當(dāng)然,我將把它建模為一個(gè) “多對(duì)多” 關(guān)系,因?yàn)槲蚁M业膮①愓呖梢詤⒓佣囗?xiàng)比賽。
沒有架構(gòu)的域建模
Google App Engine 在 Bigtable 上面的抽象不是一個(gè)面向?qū)ο蟮某橄螅患矗也荒茉瓨哟鎯?chǔ)關(guān)系,但可以共享鍵。因此,為建模多個(gè) Race和多個(gè) Runner之間的關(guān)系,我將在每個(gè) Race實(shí)例中存儲(chǔ)一列 Runner鍵,并在每個(gè) Runner實(shí)例中存儲(chǔ)一列 Race鍵。
我必須對(duì)我的鍵共享機(jī)制添加一點(diǎn)邏輯,但是,因?yàn)槲蚁M傻?API 比較自然 —我不想詢問一個(gè) Race以獲取一列 Runner鍵,因此我想要一列 Runner。幸運(yùn)的是,這并不難實(shí)現(xiàn)。
在清單 9 中,我已經(jīng)添加了兩個(gè)方法到 Race實(shí)例。但一個(gè) Runner實(shí)例被傳遞到 addRunner方法時(shí),它的對(duì)應(yīng) id被添加到底層 entity的 runners屬性中駐留的 id的 Collection。如果有一個(gè)現(xiàn)成的 runners的 collection,則新的 Runner實(shí)例鍵將添加到它;否則,將創(chuàng)建一個(gè)新的 Collection,且這個(gè) Runner的鍵(實(shí)體上的 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) )
- }
- }
當(dāng)清單 9 中的 getRunners方法調(diào)用時(shí),一個(gè) Runner實(shí)例集合將從底層的 id集合創(chuàng)建。這樣,一個(gè)新方法(getEntity)將在 Model類中創(chuàng)建,如清單 10 所示:
清單10. 從一個(gè)id 創(chuàng)建一個(gè)實(shí)體
- def getEntity(entityType, id){
- def key = KeyFactory.createKey(entityType, id)
- return this.@datastore.get(key)
- }
getEntity方法使用 Google 的 KeyFactory類來創(chuàng)建底層鍵,它可以用于查找數(shù)據(jù)存儲(chǔ)中的一個(gè)單獨(dú)實(shí)體。
最后,定義一個(gè)新的構(gòu)造函數(shù)來接受一個(gè)實(shí)體類型,如清單 11 所示:
清單11. 一個(gè)新添加的構(gòu)造函數(shù)
- public Model(Entity entity){
- this.@entity = entity
- }
如清單 9、10和 11、以及 圖 1的對(duì)象模型所示,我可以將一個(gè) Runner添加到任一 Race,也可以從任一Race獲取一列 Runner實(shí)例。在清單 12 中,我在這個(gè)等式的 Runner方上創(chuàng)建了一個(gè)類似的聯(lián)系。清單 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) )
- }
- }
這樣,我就使用一個(gè)無模式數(shù)據(jù)存儲(chǔ)創(chuàng)建了兩個(gè)域?qū)ο蟆?/p>
通過一些參賽者完成這個(gè)比賽
此前我所做的是創(chuàng)建一個(gè) Runner實(shí)例并將其添加到一個(gè) Race。如果我希望這個(gè)關(guān)系是雙向的,如圖1中我的對(duì)象模型所示,那么我也可以添加一些 Race實(shí)例到一些Runner,如清單 13 所示:
清單 13. 參加多個(gè)比賽的多個(gè)參賽者
- def runner = new Runner([fname:"Chris", lname:"Smith", date:34])
- runner.save()
- race.addRunner(runner)
- race.save()
- runner.addRace(race)
- runner.save()
將一個(gè)新的 Runner添加到 race并添加對(duì)Race的save的調(diào)用后,這個(gè)數(shù)據(jù)存儲(chǔ)已使用一列ID 更新,如圖 3 中的屏幕快照所示:
圖3. 查看一項(xiàng)比賽中的多個(gè)參賽者的新屬性
通過仔細(xì)檢查Google App Engine 中的數(shù)據(jù),可以看到,一個(gè)Race實(shí)體現(xiàn)在擁有了一個(gè)Runners 的list,如圖 4 所示。
圖4. 查看新的參賽者列表
同樣,在將一個(gè) Race添加到一個(gè)新創(chuàng)建的 Runner實(shí)例之前,這個(gè)屬性并不存在,如圖 5 所示。
圖5. 一個(gè)沒有比賽的參賽者
但是,將一個(gè) Race關(guān)聯(lián)到一個(gè) Runner后,數(shù)據(jù)存儲(chǔ)將添加新的 races ids 的 list。
圖6. 一個(gè)參加比賽的參賽者
無模式數(shù)據(jù)存儲(chǔ)的靈活性正在刷新 —屬性按照需要自動(dòng)添加到底層存儲(chǔ)。作為開發(fā)人員,我無須更新或更改架構(gòu),更談不上部署架構(gòu)了!
NoSQL 的利弊
當(dāng)然,無模式數(shù)據(jù)建模也有利有弊。回顧上面的比賽應(yīng)用程序,它的一個(gè)優(yōu)勢(shì)是非常靈活。如果我 決定將一個(gè)新屬性(比如 SSN)添加到一個(gè) Runner,我不必進(jìn)行大幅更改 —事實(shí)上,如果我將該屬性包含在構(gòu)造函數(shù)的參數(shù)中,那么它就會(huì)自動(dòng)添加。對(duì)那些沒有使用一個(gè) SSN 創(chuàng)建的舊實(shí)例而言,發(fā)生了什么事情?什么也沒發(fā)生!它們擁有一個(gè)值為 null的字段。
另一方面,我已經(jīng)明確表明要犧牲一致性和完整性來換取效率。這個(gè)應(yīng)用程序的當(dāng)前數(shù)據(jù)架構(gòu)沒有 向我施加任何限制 —理論上我可以為同一個(gè)對(duì)象創(chuàng)建無限個(gè)實(shí)例。在 Google App Engine 引擎的鍵處理機(jī)制下,它們都有惟一的鍵,但其他屬性都是一致的。更糟糕的是,級(jí)聯(lián)刪除不存在,因此如果我使用相同的技術(shù)來建模一個(gè) “一對(duì)多” 關(guān)系并刪除父節(jié)點(diǎn),那么我得到一些無效的子節(jié)點(diǎn)。當(dāng)然,我可以實(shí)現(xiàn)自己的完整性檢查 —但關(guān)鍵是,我必須親自動(dòng)手(就像完成其他任務(wù)一樣)。
使用無模式數(shù)據(jù)存儲(chǔ)需要嚴(yán)明的紀(jì)律。如果我創(chuàng)建各種類型的 Races —有些有名稱,有些沒有,有些有 date屬性,而另一些有 race_date屬性 —那么我只是在搬起石頭砸自己(或使用我的代碼的人)的腳。
當(dāng)然,也有可能聯(lián)合使用 JDO、JPA 和 Google App Engine。在多個(gè)項(xiàng)目上使用過關(guān)系模型和無模式模型后,我可以說 Gaelyk 的低級(jí) API 最靈活,使用最方便。使用 Gaelyk 的另一個(gè)好處是能夠深入了解 Bigtable 和一般的無模式數(shù)據(jù)存儲(chǔ)。
結(jié)束語
流行時(shí)尚來了又去,有時(shí)無需理會(huì)它們(明智的建議來自一個(gè)衣櫥里滿是休閑服的家伙)。但 NoSQL 看起來不太像一種時(shí)尚,更像是高度可伸縮的 Web 應(yīng)用程序開發(fā)的一個(gè)新興基礎(chǔ)。NoSQL 數(shù)據(jù)庫(kù)不會(huì)替代 RDBMS,但是,它們將補(bǔ)充它。無數(shù)成功的工具和框架基于關(guān)系數(shù)據(jù)庫(kù),RDBMSs 本身似乎沒有面臨過時(shí)的危險(xiǎn)。
總之,NoSQL 數(shù)據(jù)庫(kù)的作用是向?qū)ο?mdash;—關(guān)系數(shù)據(jù)模型提供一個(gè)及時(shí)的替代方案。它們向我們展示,有些事情是可行的,并且對(duì)于一些特定的、高度強(qiáng)制的用例甚至更好。無模式 數(shù)據(jù)庫(kù)最適用于需要高速數(shù)據(jù)檢索和可伸縮性的多節(jié)點(diǎn) Web 應(yīng)用程序。它們還有一個(gè)極好的副作用,即允許開發(fā)人員從一個(gè)面向域的視角、而不是關(guān)系視角進(jìn)行數(shù)據(jù)建模。