NoSQL數(shù)據(jù)建模的示例分析-創(chuàng)新互聯(lián)

這篇文章將為大家詳細(xì)講解有關(guān)NoSQL數(shù)據(jù)建模的示例分析,小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。

成都創(chuàng)新互聯(lián)公司長(zhǎng)期為上千家客戶提供的網(wǎng)站建設(shè)服務(wù),團(tuán)隊(duì)從業(yè)經(jīng)驗(yàn)10年,關(guān)注不同地域、不同群體,并針對(duì)不同對(duì)象提供差異化的產(chǎn)品和服務(wù);打造開放共贏平臺(tái),與合作伙伴共同營(yíng)造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為商州企業(yè)提供專業(yè)的網(wǎng)站制作、網(wǎng)站建設(shè),商州網(wǎng)站改版等技術(shù)服務(wù)。擁有十余年豐富建站經(jīng)驗(yàn)和眾多成功案例,為您定制開發(fā)。

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è)無(wú)模式數(shù)據(jù)存儲(chǔ)(比如 Bigtable)來進(jìn)行數(shù)據(jù)建模則需要您重新思考您的做事方式。但是,如果您從域模型開始設(shè)計(jì)您的應(yīng)用程序,那么 Bigtable 的無(wú)模式結(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),域模型下的無(wú)模式結(jié)構(gòu)的靈活性正在重新煥發(fā)生機(jī)。

從關(guān)系數(shù)據(jù)模型遷移到無(wú)模式數(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)建它的 “綠地效果”。在此過程中,您將深入了解無(wú)模式數(shù)據(jù)存儲(chǔ)。

實(shí)體和關(guān)系

無(wú)模式數(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. 比賽和參賽者

NoSQL數(shù)據(jù)建模的示例分析

如果我要使用一個(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)單。

無(wú)模式數(shù)據(jù)存儲(chǔ)的好處之一是無(wú)須事先知道所有事情,也就是說,與使用關(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ū)ο蟠恚?。相反,我將把我的時(shí)間花費(fèi)在確定如何查找對(duì)象并處理關(guān)系上。這是 NoSQL 和各種利用無(wú)模式數(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

NoSQL數(shù)據(jù)建模的示例分析

查找程序方法生成持久存儲(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),這是分頁(yè)限制 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è)無(wú)模式數(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è)參賽者的新屬性

NoSQL數(shù)據(jù)建模的示例分析

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

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

NoSQL數(shù)據(jù)建模的示例分析

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

圖5. 一個(gè)沒有比賽的參賽者

NoSQL數(shù)據(jù)建模的示例分析

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

圖6. 一個(gè)參加比賽的參賽者

NoSQL數(shù)據(jù)建模的示例分析

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

NoSQL 的利弊

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

使用無(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)系模型和無(wú)模式模型后,我可以說 Gaelyk 的低級(jí) API 最靈活,使用最方便。使用 Gaelyk 的另一個(gè)好處是能夠深入了解 Bigtable 和一般的無(wú)模式數(shù)據(jù)存儲(chǔ)。

關(guān)于“NoSQL數(shù)據(jù)建模的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,使各位可以學(xué)到更多知識(shí),如果覺得文章不錯(cuò),請(qǐng)把它分享出去讓更多的人看到。

當(dāng)前名稱:NoSQL數(shù)據(jù)建模的示例分析-創(chuàng)新互聯(lián)
本文URL:http://bm7419.com/article44/dcocee.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供外貿(mào)網(wǎng)站建設(shè)、網(wǎng)站制作做網(wǎng)站、定制網(wǎng)站全網(wǎng)營(yíng)銷推廣、網(wǎng)站導(dǎo)航

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

h5響應(yīng)式網(wǎng)站建設(shè)