MongoDB, Java 與對(duì)象關(guān)系映射
Posted on 2012-05-09 13:46 一酌散千憂 閱讀(1546) 評(píng)論(0) 編輯 收藏 所屬分類: 翻譯 、NoSQLMongoDB介紹
當(dāng)今NoSQL領(lǐng)域中有很多有力的競(jìng)爭(zhēng)者通過(guò)多種方式來(lái)處理海量數(shù)據(jù)問(wèn)題。其中重要的解決方案之一就是MongoDB。MongoDB是面向文檔的弱結(jié)構(gòu)化存儲(chǔ)方案,使用JSON格式來(lái)展現(xiàn)、查詢和修改數(shù)據(jù)。
MongoDB文檔相當(dāng)完備,擴(kuò)展規(guī)模與安裝一樣簡(jiǎn)單。它提供冗余、切片、索引以及map/reduce等概念支持。MongoDB的開(kāi)源社區(qū)非常大且非常活躍。MongoDB在很多大型產(chǎn)品中被實(shí)際運(yùn)用,如:Disney, Craigslist, Foursquare, Github 和SourceForge。MongoDB是一個(gè)開(kāi)源項(xiàng)目,由10gen.com建立并維護(hù),該公司由DoubleClick的前任執(zhí)行人員創(chuàng)立。同時(shí),10gen也提供了極好的商業(yè)支持與參與建設(shè)。
MongoDB 與 NoSQL: 缺陷與優(yōu)勢(shì)
MongoDB作為一個(gè)可用NoSQL方案具有很多優(yōu)勢(shì)。我剛開(kāi)始接觸NoSQL數(shù)據(jù)庫(kù)了解了一系列基于Java的方案,并且花了大量的時(shí)間來(lái)弄懂什么是列家族,Hadoop與HBase的關(guān)系,ZooKeeper到底是什么。當(dāng)我終于全部清楚之后,發(fā)現(xiàn)Cassandra與HBase確實(shí)是對(duì)于NoSQL領(lǐng)域非常可靠、可信賴的解決方案。但與其他的解決方案相比,MongoDB讓我在能夠開(kāi)始寫代碼之前,不用理解那么多的概念。
與其他軟件相似,MongoDB也存在缺陷。經(jīng)過(guò)一段時(shí)間使用MongoDB,我列舉經(jīng)歷過(guò)并需要注意的一些事情,我成為“Gotchas”:
- 不要按照關(guān)系型數(shù)據(jù)庫(kù)來(lái)思考。這很明顯,MongoDB使得構(gòu)建和執(zhí)行復(fù)雜查詢變得非常容易。當(dāng)實(shí)際使用的時(shí)候,你會(huì)主要關(guān)注于效率問(wèn)題(像我一樣)。
- MongoDB的索引是二進(jìn)制的樹(shù)。如果你不是很熟悉B-tree,可能需要了解一下。這些都涉及到構(gòu)建符合提供查詢條件需求的建立索引的方式。
- 小心的設(shè)計(jì)索引結(jié)構(gòu)。這涉及到上面提到的B-tree。剛開(kāi)始我的索引包含文檔中的很多字段,以防我會(huì)使用到他們。不要犯同樣的錯(cuò)誤。我有一個(gè)很小集合的索引(大約1千萬(wàn)記錄)增長(zhǎng)到超過(guò)17GB的空間,比集合本身還大。你應(yīng)該不會(huì)想要索引一個(gè)包含成百上千個(gè)實(shí)體的列表字段。
- MongoDB采用了非常有意思的方式來(lái)實(shí)現(xiàn)NoSQL:采用BSON作為存儲(chǔ),JSON作為展示,JavaScript用于管理和Map/Reduce。因此也引起了一些小問(wèn)題比如這個(gè) (破壞了Number和Long的相等操作),在MongoDB逐漸流行之后,可能會(huì)不斷的展示出來(lái)。
MongoDB, 命令行與驅(qū)動(dòng)
MongoDB基本是使用JavaScript客戶端命令行程序來(lái)進(jìn)行復(fù)雜任務(wù)管理的,如數(shù)據(jù)整合和簡(jiǎn)單信息處理,編程都是完全使用JavaScript語(yǔ)言來(lái)的。本文中,我們會(huì)展示命令行的使用示例。現(xiàn)在有大量的MongoDB客戶端產(chǎn)品提供,并且由MongoDB社區(qū)來(lái)支持驅(qū)動(dòng)。通常每種編程語(yǔ)言都有驅(qū)動(dòng),并且所有流行的語(yǔ)言都有包括,一些不那么流行的也包含在內(nèi)。這篇文章展示了使用MongoDB的Java驅(qū)動(dòng),并使用一個(gè)ORM庫(kù)(MJORM)與之進(jìn)行比較。
介紹 MJORM: MongoDB的ORM方案
在解決的眾多有意思的問(wèn)題中,最近NoSQL數(shù)據(jù)存儲(chǔ)在開(kāi)發(fā)者中主要的問(wèn)題趨勢(shì)就是對(duì)象關(guān)系映射。對(duì)象關(guān)系映射就是將傳統(tǒng)中保存在關(guān)系型數(shù)據(jù)庫(kù)中的持久化數(shù)據(jù)映射為在應(yīng)用程序中使用的對(duì)象。這使得編程語(yǔ)言使用起來(lái)更加流暢和自然。
MongoDB面向文檔的架構(gòu)使得它非常適合對(duì)象關(guān)系映射,因?yàn)槲臋n本身就是以對(duì)象形式存儲(chǔ)的。可惜沒(méi)有太多的MongoDB的Java對(duì)象關(guān)系映射庫(kù),但是還是有一些,如morphia-(A type-safe Java library for MongoDB), spring-data(SpringData項(xiàng)目的MongoDB實(shí)現(xiàn))
這些ORM庫(kù)大量使用了注解,因?yàn)橐恍┰驅(qū)ξ也贿m合,其中最重要的就是這些被注解的對(duì)象在多個(gè)項(xiàng)目中的兼容性問(wèn)題。這讓我開(kāi)始了mongo-Java-orm 或者 "MJORM" (發(fā)音 me-yorm)項(xiàng)目,一個(gè)MongoDB的Java對(duì)象關(guān)系映射項(xiàng)目。MJORM是在MIT許可之下,并且在發(fā)布在了google code project。項(xiàng)目采用maven構(gòu)建,并且maven構(gòu)件倉(cāng)庫(kù)托管于google code版本控制服務(wù)器。MJORM的最新可用發(fā)布版本為0.15,已經(jīng)由一些項(xiàng)目使用與生產(chǎn)環(huán)境中。
開(kāi)始使用ORM
加入MJORM 庫(kù)
Maven的使用者首先應(yīng)當(dāng)在pom.xml中加入MJORM的maven倉(cāng)庫(kù),使得MJORM構(gòu)件可用。
<repository>
<id>mjorm-webdav-maven-repo</id>
<name>mjorm maven repository</name>
<url>http://mongo-Java-orm.googlecode.com/svn/maven/repo/</url>
<layout>default</layout>
</repository>
然后加入依賴:
<dependency>
<groupId>com.googlecode</groupId>
<artifactId>mongo-Java-orm</artifactId>
<version>0.15</version>
</dependency>
這樣就可以在應(yīng)用中引入MJORM代碼。假如沒(méi)有使用maven,則你需要手動(dòng)下載MJORM的pom.xml中列舉的所有依賴。
建立 POJOs
依賴已經(jīng)導(dǎo)入,可以開(kāi)始編碼了。我們從POJO開(kāi)始:
class Author {
private String firstName;
private String lastName;
// ... setters and getters ...
}
class Book {
private String id;
private String isbn;
private String title;
private String description;
private Author author;
// ... setters and getters ...
}
我們?cè)谶@個(gè)對(duì)象模型中的描述是,作者有ID、姓和名,書有ID、ISNB、標(biāo)題、描述和作者。
你可能注意到書的id屬性是一個(gè)字符串,這是為了適應(yīng)MongoDB的對(duì)象ID類型。MongoDB的ID是一個(gè)12字節(jié)的二進(jìn)制值顯示為一個(gè)十六進(jìn)制的字符串。MongoDB要求集合中的每個(gè)文檔都必須有一個(gè)唯一id,但不要求一定要是ObjectId。目前MJORM只支持ObjectId,并且顯示為字符串。
你也可能注意到了Author沒(méi)有id字段。這是因?yàn)锽ook是它的父文檔,因此不需要有id。記住,MongoDB只要求集合中的文檔在根級(jí)別的id。
創(chuàng)建XML映射文件
下一個(gè)步驟就是建立XML映射文件,MJORM能夠?qū)ongoDB文檔轉(zhuǎn)換為對(duì)象。我們?yōu)槊總€(gè)文檔創(chuàng)建一個(gè)對(duì)象作為示范,無(wú)論將所有的映射放在一個(gè)XML文件中還是分開(kāi)都是可以的。
Author.mjorm.xml
:
<?xml version="1.0"?>
<descriptors>
<object class="Author">
<property name="firstName" />
<property name="lastName" />
</object>
</descriptors>
Book.mjorm.xml
:
<?xml version="1.0"?>
<descriptors>
<object class="Book">
<property name="id" id="true" auto="true" />
<property name="isbn" />
<property name="title" />
<property name="description" />
<property name="author" />
</object>
</descriptors>
這些映射文件能夠很好的自解釋。descriptors
元素是根元素,必須包含在每個(gè)映射文件中。在它下面是object
元素定義了文檔與之對(duì)應(yīng)的類。Object
包含的
property
元素主要用于描述POJO中的屬性以及這些屬性如何與MongoDB中的文檔想對(duì)應(yīng)。property
元素至少必須包含一個(gè)name
屬性,這個(gè)元素就是POJO和MongoDB的文檔中的屬性名稱。column
屬性則是可選的,用于特定一個(gè)在MongoDB文檔中的可選屬性名稱。
property
元素當(dāng)中的id屬性應(yīng)該是對(duì)象的唯一識(shí)別。一個(gè)對(duì)象只能有一個(gè)property
元素包含id屬性。auto
的設(shè)置會(huì)使得MJORM在持久化時(shí)為該屬性自動(dòng)生成一個(gè)值。
可以在google code的MJORM項(xiàng)目主頁(yè)中查看XML映射文件的更多細(xì)節(jié)描述。
整合POJO與XML
我們創(chuàng)建了數(shù)據(jù)模型以及映射文件,使得MJORM可以從MongoDB序列號(hào)以及反序列號(hào)POJO。我們可以進(jìn)行一些有意思的事情了,首先打開(kāi)MongoDB的鏈接:
Mongo mongo = new Mongo(
new MongoURI("mongodb://localhost/mjormIsFun")); // 10gen driver
Mongo
對(duì)象是由10gen編寫的Java驅(qū)動(dòng)提供的。示例中連接了一個(gè)本地的MongoDB實(shí)例中的mjormIsFun數(shù)據(jù)庫(kù)。接下來(lái)我們創(chuàng)建MJORM ObjectMapper
。目前ObjectMapper
在MJORM中的唯一實(shí)現(xiàn)就是XmlDescriptorObjectMapper
,使用XML結(jié)構(gòu)描述信息。可能之后會(huì)增加對(duì)注解或其他結(jié)構(gòu)定義的支持。
XmlDescriptorObjectMapper objectMapper = new XmlDescriptorObjectMapper();
mapper.addXmlObjectDescriptor(new File("Book.mjorm.xml"));
mapper.addXmlObjectDescriptor(new File("Author.mjorm.xml"));
建立好了XmlDescriptorObjectMapper
并且加入了映射文件。接下來(lái)建立由MJORM提供的MongoDao
對(duì)象的實(shí)例。
DB db = mongo.getDB("mjormIsFun"); // 10gen driver
MongoDao dao = new MongoDaoImpl(db, objectMapper);
首先我們要獲得10gen驅(qū)動(dòng)提供的DB對(duì)象實(shí)例。然后使用DB和ObjectMapper
建立MongoDao
。我們準(zhǔn)備開(kāi)始持久化數(shù)據(jù),建立一個(gè)Book
然后保存到MongoDB中。
Book book = new Book();
book.setIsbn("1594743061");
book.setTitle("MongoDB is fun");
book.setDescription("...");
book = dao.createObject("books", book);
System.out.println(book.getId()); // 4f96309f762dd76ece5a9595
首先建立Book
對(duì)象并且填值,然后調(diào)用MongoDao
的 createObject
方法,將Book
對(duì)象傳入"books
" 的集合中。MJORM會(huì)按照之前的xml映射文件將Book
轉(zhuǎn)換為DBObject
(這是10gen的Java驅(qū)動(dòng)使用的基本類型),并保存一個(gè)新的文檔進(jìn)"books
" 集合。MJORM返回Book對(duì)象時(shí),id屬性會(huì)被填充。請(qǐng)注意,MongoDB默認(rèn)是不需要在使用前建立數(shù)據(jù)庫(kù)或集合的,系統(tǒng)會(huì)在需要時(shí)自動(dòng)創(chuàng)建,這可能會(huì)造成某些困擾。在MongoDB的命令行中查看Book對(duì)象大概如下:
> db.books.find({_id:ObjectId("4f96309f762dd76ece5a9595")}).pretty()
{
"_id": ObjectId("4f96309f762dd76ece5a9595"),
"isbn": "1594743061",
"title": "MongoDB is fun",
"description": "..."
}
我們來(lái)看看假如不用MJORM而直接使用10gen的Java驅(qū)動(dòng),如何使用createObject
方法:
Book book = new Book();
book.setIsbn("1594743061");
book.setTitle("MongoDB is fun");
book.setDescription("...");
DBObject bookObj = BasicDBObjectBuilder.start()
.add("isbn", book.getIsbn())
.add("title", book.getTitle())
.add("description", book.getDescription())
.get();
// 'db' is our DB object from earlier
DBCollection col = db.getCollection("books");
col.insert(bookObj);
ObjectId id = ObjectId.class.cast(bookObj.get("_id"));
System.out.println(id.toStringMongod()); // 4f96309f762dd76ece5a9595
下面進(jìn)行對(duì)象的查詢:
Book book = dao.readObject("books", "4f96309f762dd76ece5a9595", Book.class);
System.out.println(book.getTitle()); // "MongoDB is fun"
readObject
方法根據(jù)給定文檔的id從指定的集合中讀取文檔,轉(zhuǎn)換為對(duì)象(再次使用映射文件)并返回。
敏銳的讀者會(huì)注意到Book還沒(méi)有指定Author,仍然保存了。這歸咎于MongoDB的結(jié)構(gòu)不敏感的特性。我們不能要求集合中的文檔包含所有屬性(id屬性是必須的),所有在MongoDB中沒(méi)有Author的Book是可以的。我們現(xiàn)在為Book添加一個(gè)Author并且更新一下:
Author author = new Author();
author.setFirstName("Brian");
author.setLastName("Dilley");
book.setAuthor(author);
dao.updateObject("books", "4f96309f762dd76ece5a9595", book);
現(xiàn)在Book就包含了Author,并且在MongoDB中持久化了。現(xiàn)在在命令行查看了Book:
> db.books.find({_id:ObjectId("4f96309f762dd76ece5a9595")}).pretty()
{
"_id": ObjectId("4f96309f762dd76ece5a9595"),
"isbn": "1594743061",
"title": "MongoDB is fun",
"description": "..."
"author": {
"firstName": "Brian",
"lastName": "Dilley"
}
}
可以看到持久化的Book中已經(jīng)包含了author。不使用MJORM來(lái)操作一遍:
Author author = new Author();
author.setFirstName("Brian");
author.setLastName("Dilley");
book.setAuthor(author);
DBObject bookObj = BasicDBObjectBuilder.start()
.add("isbn", book.getIsbn())
.add("title", book.getTitle())
.add("description", book.getDescription())
.push("author")
.add("firstName", author.getFirstName())
.add("lastName", author.getLastName())
.pop()
.get();
DBCollection col = db.getCollection("books");
col.update(new BasicDBObject("_id", bookObj.get("_id")), bookObj);
對(duì)于MongoDao
方法的深入討論已經(jīng)超出了本文的范圍。對(duì)于將MJORM有興趣用于實(shí)際項(xiàng)目中的用戶強(qiáng)烈建議了解一下MJORM項(xiàng)目提供的相關(guān)文檔,或者MongoDao
接口提供的相關(guān)用法。
總結(jié)
希望這篇文章對(duì)MongoDB和MJORM的亮點(diǎn)有所展示。MongDB是一個(gè)優(yōu)秀的呃NoSQL數(shù)據(jù)存儲(chǔ),有著大量?jī)?yōu)秀的特性,會(huì)是NoSQL市場(chǎng)中長(zhǎng)期競(jìng)爭(zhēng)者。若你會(huì)在一個(gè)Java項(xiàng)目中使用MongoDB,希望你也能夠考慮使用MJORM作為你的ORM框架。十分歡迎大家提交特性需求、錯(cuò)誤異常報(bào)告、文檔和源碼修正。
作者 Bio
Brian Dilley 是一個(gè)經(jīng)驗(yàn)豐富的高級(jí)工程師以及項(xiàng)目領(lǐng)導(dǎo),在Java/Java EE /Spring Framework/Linux內(nèi)部結(jié)構(gòu)理解和管理有著超過(guò)13年的經(jīng)驗(yàn)。Brian對(duì)于創(chuàng)業(yè)公司有很多經(jīng)驗(yàn),推向市場(chǎng),構(gòu)建/維護(hù)產(chǎn)品等。他是Iaas、cloud、PHP和Linux的專家,熟悉產(chǎn)品的采購(gòu)、安裝及配置定義,以及公司的軟硬件架構(gòu)包括負(fù)載均衡、數(shù)據(jù)庫(kù)、微博等。可以follow Brian的 Twitter 。