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