文章目錄
- MongoDB簡介
- 將MJORM庫添加到項目裡
- 建立POJO
- 建立XML對應檔
- 整合
MongoDB簡介
目前有很多互相競爭的NoSQL產品,它們使用的方式不盡相同,但都能很好地解決大資料問題。MongoDB就是其中一款非常不錯的產品。MongoDB是面向文檔、無Schema的儲存解決方案,它用JSON風格的文檔展現、查詢、修改資料。
MongoDB有很豐富的文檔,安裝和設定都很簡單,而且易於擴充。它支援大家熟知的複製、分區、索引和Map/Reduce等概念。MongoDB開源社區的規模很大,也很活躍。讓MongoDB引以為豪的是,包括Disney、Craigslist、Foursquare、Github和SourceForge在內的大型、高流量生產環境都已經部署了MongoDB。MongoDB是個開源項目,由DoubleClick前高管們創辦的10gen.com公司建立和維護。除了很積極地參與社區支援外,10gen也供應商業支援。
MongoDB和NoSQL的優劣勢
作為NoSQL解決方案,MongoDB的優勢是很容易上手。在我第一次深入研究NoSQL資料庫的時候,嘗試了很多基於Java的解決方案,我發現要搞清楚什麼是列族(column family)、Hadoop和HBase之間是什麼關係、ZooKeeper到底是什麼非常費時間。我最終想明白這些問題的時候,才明白Cassandra、HBase等產品都是非常完善的NoSQL解決方案。和其他解決方案相比,MongoDB則更容易掌握一些,開始寫代碼之前不需要理解太多的概念。
很顯然,MongoDB和任何軟體一樣都存在缺陷。在學習、使用MongoDB的過程中,我遇到過幾件可算是“陷阱”的事情:
- 不要把它用成RDBMS。這一點看起來顯而易見,但在MongoDB裡建立、執行複雜查詢都很容易,以至於到了想用它做即時查詢的時候,你可能才會發現自己已經做過頭了,而且可能會碰到效能問題。(我以前就犯過這樣的錯)
- MongoDB的索引是Binary Tree。如果你不太熟悉B-Tree,應該研究一下。查詢條件的順序要和建立索引的順序相匹配。
- 精心設計索引。這和前面提到的B-Tree有關係。我剛開始建立的幾個索引都包含文檔裡的很多欄位,因為總是想著以後可能會查詢它們,這種想法你應該能夠理解。不要犯這樣的錯誤。我曾給一個很小的集合(大約一千萬條記錄)建立了一個索引,這個索引後來增長到17GB,比集合本身還要大。如果某個數組欄位可能會包含成百上千的條目,你可能不會給它建立索引。
- MongoDB支援NoSQL的方法非常有趣,它用BSON儲存、用JSON表示,管理和Map/Reduce則用了JavaScript。這樣一來,等MongoDB發展的時間足夠久、和更流行的大資料解決方案一樣長的時候,MongoDB必然會出現一些奇怪的小問題,比如在NumberLong上使用等於運算子會判斷失敗。
MongoDB、控制台、驅動程式
MongoDB的管理通常可以在一個JavaScript用戶端控制台應用上進行,控制台應用能簡化資料移轉和操作等複雜任務;你也完全可以用JavaScript語言編程實現MongoDB的管理。在這篇文章裡,我們會示範控制台的使用。現在的MongoDB用戶端產品非常多,它們都具備能投入生產環境的品質,MongoDB社區也稱它們為驅動程式。一般來說,每種程式設計語言都有各自的驅動程式,這些驅動程式能覆蓋所有流行的程式設計語言,還有一些並不是很流行的程式設計語言。本文將展示MongoDB的Java驅動程式該如何使用,也會和使用ORM庫(MJORM)的方式進行比較。
MJORM簡介:MongoDB的ORM解決方案
NoSQL資料存放區還有很多有趣的問題需要解決,最近讓應用程式員比較關心的是對象關係映射(ORM)。ORM是指持久化資料和應用所用對象之間的映射,持久化資料過去都儲存在關係型資料庫裡。ORM能讓處理資料的過程更加流暢、更加貼近編寫應用的語言。
MongoDB面向文檔的架構讓它很容易進行ORM,因為它儲存的文檔本身就是對象。不過可惜的是,可用於MongoDB的Java ORM庫還不是很多,目前只有morphia(針對MongoDB的Java庫,是型別安全的)和spring-data(Spring Data綜合項目的MongoDB實現)。
這些ORM庫使用了大量註解,出於很多原因,我並不傾向於使用註解,其中最重要的是被註解的對象在多重專案之間的可移植性問題。所以我建立了mongo-Java-orm項目(MJORM,發音為me-yorm),它是針對MongoDB的Java ORM。MJORM使用MIT許可,放在了Google Code上。項目用Maven構建,Maven的工件庫目前託管在Google Code的Subversion伺服器上。寫這篇文章的時候,MJORM最新的穩定發布版本是0.15,個別項目已經在生產環境裡使用了。
MJORM入門將MJORM庫添加到項目裡
Maven使用者首先要將MJORM的Maven倉庫添加到pom.xml檔案裡,以便自己的項目能使用MJORM工件:
<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>
這樣你就能把MJORM類匯入到自己的應用裡並使用它們。如果你沒用Maven,那你需要手動下載MJORM庫,還有MJORM pom.xml裡列出的所有依賴。
建立POJO
依賴關係處理好之後,就開始編寫代碼吧。我們先編寫Java 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、ISBN號、標題、描述資訊和作者。
可以看到書的ID屬性是一個String,它會適應成MongoDB的ObjectId類型,ObjectId類型是個十二位元組的二進位值,用十六進位的字串來表示。雖然MongoDB要求所有集合裡的每個文檔都要有一個唯一的ID,但並沒有要求ID必須是ObjectId類型。目前MJORM支援的ID類型只有ObjectId,而且會把它們表示成String。
你可能已經注意到,Author對象沒有ID。這是因為Author是Book文檔的子文檔,所以就沒必要非得有一個ID了。請記住,MongoDB的ID只需要放在一個集合的根層級文檔中。
建立XML對應檔
下一步是建立XML對應檔,MJORM會用這些對應檔把MongoDB文檔映射成對象。在本文的示範裡,我們會給兩個對象各建立一個文檔,但真正合理的做法是把所有的映射都放在一個XML檔案裡,或者根據實際需要進行分割。
下面是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元素是根項目,所有的對應檔都要有。根項目下面是object元素,用來定義要被映射到MongoDB文檔的類。object會包含property元素,用來描述POJO的所有屬性,以及它們怎樣映射到MongoDB文檔的屬性。property元素至少要有一個name屬性,這是POJO屬性的名稱,也是MongoDB文件屬性的名稱。property元素還可以添加一個column屬性,指定MongoDB文檔裡備用的屬性名稱。
包含id屬性的property元素會被看作是對象的唯一識別碼。一個object元素可以只包含一個帶有id屬性的property元素。auto屬性是讓MJORM在持久化這個屬性時給它自動產生一個值。
要想瞭解有關XML對應檔更詳細的說明,請移步至Google Code上的MJORM項目。
整合
我們現在已經建立好了資料模型,還有告訴MJORM在資料寫入MongoDB時如何解析POJO、從MongoDB讀取資料時如何封裝POJO的對應檔,那我們就可以開始一段有趣的學習之旅了。首先我們必須開啟到MongoDB的串連:
Mongo mongo = new Mongo( new MongoURI("mongodb://localhost/mjormIsFun")); // 10gen驅動程式
Mongo對象來自10gen員工編寫的Java驅動程式。這個例子開啟了一個到本地MongoDB執行個體的串連,使用mjormIsFun資料庫。接下來我們建立MJORM裡的ObjectMapper。目前MJORM裡可用的ObjectMapper介面實現只有XmlDescriptorObjectMapper,它使用前面的XML Schema,MJORM以後的實現可能會支援註解或其他配置機制。
XmlDescriptorObjectMapper objectMapper = new XmlDescriptorObjectMapper(); mapper.addXmlObjectDescriptor(new File("Book.mjorm.xml")); mapper.addXmlObjectDescriptor(new File("Author.mjorm.xml"));
我們建立了XmlDescriptorObjectMapper對象,並添加了對應檔。下一步我們會建立一個MJORM提供的MongoDao對象執行個體:
DB db = mongo.getDB("mjormIsFun"); // 10gen驅動程式 MongoDao dao = new MongoDaoImpl(db, objectMapper);
我們先擷取了一個10gen驅動程式裡的DB對象執行個體。然後用DB對象和先前建立的ObjectMapper來建立MongoDao。現在已經做好了持久化資料的準備,那讓我們建立一個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對象,賦值之後調用了MongoDao的createObject方法,兩個參數分別是集合名稱“books”和Book對象。MJORM接著會用先前建立的XML對應檔把Book轉換成DBObject(10gen的Java驅動程式所使用的基本物件類型),並把新的文檔持久化到“books”集合中。然後MJORM會返回Book對象的執行個體,返回的Book對象執行個體帶有產生的id屬性。重點要注意的是,MongoDB在預設情況下並不會要求建立好資料庫或集合後才能使用;MongoDB在需要的時候才會建立它們,這有時候會引起混亂。從MongoDB控制台上看到的新Book如下所示:
> db.books.find({_id:ObjectId("4f96309f762dd76ece5a9595")}).pretty() { "_id": ObjectId("4f96309f762dd76ece5a9595"), "isbn": "1594743061", "title": "MongoDB is fun", "description": "..." }
讓我們來看看如果不使用MJORM,而是直接用10gen的Java驅動程式,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’是我們先前建立的DB對象 DBCollection col = db.getCollection("books"); col.insert(bookObj); ObjectId id = ObjectId.class.cast(bookObj.get("_id")); System.out.println(id.toStringMongod()); // 4f96309f762dd76ece5a9595
現在我們來查詢一下對象:
Book book = dao.readObject("books", "4f96309f762dd76ece5a9595", Book.class); System.out.println(book.getTitle()); // "MongoDB is fun"
readObject方法用指定的id從特定集合中讀取檔案,然後將檔案轉換成相應的類(會再次使用先前的對應檔)並返回。
敏銳的你可能已經察覺到我們的Book還沒Author,但Book仍然被持久化了。這正是MongoDB的無Schema特性。除了id之外,我們不能要求集合裡的文檔包含任何屬性,所以在MongoDB裡建立沒有Author的Book是完全沒有問題的。讓我們給Book添加一位Author並更新:
Author author = new Author(); author.setFirstName("Brian"); author.setLastName("Dilley"); book.setAuthor(author); dao.updateObject("books", "4f96309f762dd76ece5a9595", book);
現在的Book包含了Author,也持久化到了MongoDB。讓我們從MongoDB控制台上看看新的Book:
> db.books.find({_id:ObjectId("4f96309f762dd76ece5a9595")}).pretty() { "_id": ObjectId("4f96309f762dd76ece5a9595"), "isbn": "1594743061", "title": "MongoDB is fun", "description": "..." "author": { "firstName": "Brian", "lastName": "Dilley" } }
正如你所看到的,持久化的Book現在包含一個作者。接著再看看不使用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的所有方法了。如果你想在項目裡使用MJORM,推薦你看看MJORM項目的文檔,或者是MJORM項目提供的MongoDao介面。
結論
希望這篇文章能讓大家對MongoDB和MJORM開始感興趣。方興未艾的MongoDB是個很優秀的NoSQL資料存放區產品,有很多很不錯的特性。如果你要在Java項目裡使用MongoDB,那你可以考慮用MJORM庫來滿足ORM需求。要是能提出功能需求、Bug報告、文檔,或給源碼打補丁,我們將不勝感激!