1. 程式人生 > >mongodb的morphia框架學習筆記

mongodb的morphia框架學習筆記

快速開始: @Entity public class Hotel {      @Id private ObjectId id;     private String name;     private int stars;      @Embedded     private Address address; } @Embedded public class Address {     private String street;     private String city;     private String postCode;     private String country; } main方法: Mongo mongo=new Mongo(); Morphia morphia = new Morphia(); morphia.map(Hotel.class).map(Address.class); Datastore ds = morphia.createDatastore(mongo, "my_database"); //儲存 ds.save(hotelToSave); //查詢四星級酒店 //List<Hotel> fourStarHotels = ds.find(Hotel.class, "stars >=", 4).asList(); List<Hotel> fourStarHotels = ds.find(Hotel.class).field("stars").greaterThanOrEq(4).asList(); ------------------------------------------------- Datastore建立後index和capped collections的初始化: ds.ensureIndexes(); ///creates all defined with @Indexed ds.ensureCaps(); //creates all collections for @Entity([email protected](...)) 這兩個方法應該在你把實體註冊到morphia之後(morphia.map(Hotel.class).map(Address.class))來呼叫,它預設會同步的建立index和capped collections,這可以在每次系統啟動時確保索引存在(包括第一次執行的時候)。 查詢一條記錄: MyEntity e = ds.find(MyEntity.class).get(); //get the first one by type MyEntity e = ds.find(MyEntity.class).field("name").equal("someName").get(); //get the first one where name = "someName" ------------------------------------------------- Datastore介面介紹: ------------------------- get方法:其實就是find方法的快捷方式,根據ID返回一個實體,或者返回null。 Hotel hotel = ds.get(Hotel.class, hotelId); ------------------------- find方法:會返回一個Query物件,支援迭代器和QueryResults介面。 //use in a loop for(Hotel hotel : ds.find(Hotel.class, "stars >", 3))    print(hotel); //get back as a list List<Hotel> hotels = ds.find(Hotel.class, "stars >", 3).asList(); //sort the results List<Hotel> hotels = ds.find(Hotel.class, "stars >", 3).sort("-stars").asList(); //get the first matching hotel, by querying with a limit(1) Hotel gsHotel = ds.find(Hotel.class, "name", "Grand Sierra").get(); //same as Hotel gsHotel = ds.find(Hotel.class, "name =", "Grand Sierra").get(); 可用的操作符列表: ["=", "==","!=", "<>", ">", "<", ">=", "<=", "in", "nin", "all", "size", "exists"] ------------------------- save方法: Hotel hotel = new Hotel(); ds.save(hotel); //@Id field is filled in for you (after the save), if you didn't set it. ObjectId id = hotel.getId(); ------------------------- delete方法:根據一個ID或者查詢物件來刪除 ds.delete(Hotel.class, "Grand Sierra Resort"); //use a query ds.delete(ds.createQuery(Hotel.class).filter("pendingDelete", true)); ------------------------- FindAndDelete方法:刪除並返回記錄的原子操作 Hotel grandSierra = ds.findAndDelete(ds.get(Hotel.class, "Grand Sierra Resort")); ------------------------- Update方法:更新部分資訊時比整體save一次要高效的多,例如更新使用者的最後登入時間: @Entity class User {    @Id private ObjectId id;    private long lastLogin;    //... other members    private Query<User> queryToFindMe()    {       return datastore.createQuery(User.class).field(Mapper.ID_KEY).equal(id);    }    public void loggedIn()    {       long now = System.currentTimeMillis();       UpdateOperations<User> ops = datastore.createUpdateOperations(User.class).set("lastLogin", now);       ds.update(queryToFindMe(), ops);       lastLogin = now;    } } 有關update方法的更多高階操作: http://code.google.com/p/morphia/wiki/Updating ------------------------------------------------- Query介面介紹: 可以新增查詢條件,排序,限定返回結果條數和位置。實現了QueryResults介面。 ------------------------- Filter方法: Query q = ds.createQuery(MyEntity.class).filter("foo >", 12); 第一個引數是屬性名和比較符,第二個引數是比較值。 多個fileter直接的關係是“與(and)” Query q = ds.createQuery(MyEntity.class).filter("foo >", 12).filter("foo <", 30); 可用操作符列表: ["=", "==","!=", "<>", ">", "<", ">=", "<=", "in", "nin", "all", "size", "exists"] ------------------------- Fluent介面: 與Fileter介面差不多,英文呢方法名,更符合使用習慣: Query q = ds.createQuery(MyEntity.class).field("foo").equal(1); 可用方法列表: [exists,doesNotExist,greaterThan,greaterThanOrEq,lessThan,lessThanOrEq,equal,notEqual,hasThisOne,hasAllOf,hasAnyOf,hasNoneOf,hasThisElement,sizeEq] 使用Fluent介面,可以使用or條件: Query<Person> q = ad.createQuery(Person.class); q.or(         q.criteria("firstName").equal("scott"),         q.criteria("lastName").equal("scott") ); ------------------------- Geo-spatial:mongoDB的地理位置操作 ------------------------- Fields方法: 類似與mongo本身的query,可以用“點號”指定屬性。 Query q = ds.createQuery(Person.class).field("addresses.city").equal("San Francisco"); //or with filter, or with this helper method Query q = ds.find(Person.class, "addresses.city", "San Francisco"); ------------------------- Validation:如果查詢時屬性名找不到,則會丟擲異常。如果用到“點號”指定屬性,則表示式每一部分都必須匹配類的結構圖。 如果伺服器可以強制轉換資料,則資料型別的錯誤會被作為警告記錄下來。 上述校驗特性可以在任何一個查詢或查詢的一部分處被關閉: Query q = ds.createQuery(MyEntity.class).disableValidation(); //or it can be disabled for just one filter Query q = ds.createQuery(MyEntity.class).disableValidation().filter("someOldField", value).enableValidation().filter("realField", otherVal); ------------------------- Sort方法:排序 Query q = ds.createQuery(MyEntity.class).filter("foo >", 12).order("dateAdded"); ... // desc order Query q = ds.createQuery(MyEntity.class).filter("foo >", 12).order("-dateAdded"); ... // asc dateAdded, desc foo Query q = ds.createQuery(MyEntity.class).filter("foo >", 12).order("dateAdded, -foo"); ------------------------- Limit方法: Query q = ds.createQuery(MyEntity.class).filter("foo >", 12).limit(100); ------------------------- offset(skip)方法:要求伺服器跳過一部分記錄,這麼做效率不如使用一些屬性的range filter,比如pagination。 Query q = ds.createQuery(MyEntity.class).filter("foo >", 12).offset(1000); ------------------------- 只返回指定的屬性:這麼做會導致不完整的物件,小心使用。 MyEntity e = ds.createQuery(MyEntity.class).retrievedFields
(true, "foo").get(); val = e.getFoo(); // 僅返回這個欄位 MyEntity e = ds.createQuery(MyEntity.class).retrievedFields(false, "foo").get(); val = e.getFoo(); // 僅不返回這個欄位 指定欄位名的引數可以是字串list或者陣列: MyEntity e = ds.createQuery(MyEntity.class).retrievedFields(true, "foo", "bar").get(); ------------------------- 取資料: 使用QueryResults介面的方法即可取到資料,這些方法不會影響Query物件本身(返回的是副本),所以可以反覆呼叫。 get()     使用limit(1),返回一條記錄 asList()     返回所有記錄,大資料量時可能較慢 fetch()     明確要求返回一個可迭代的物件 asKeyList()     返回記錄的Key<T>,只從server取id欄位 fetchEmptyEntities()     與fetch類似,但只取id欄位 Query q = ds.createQuery(MyEntity.class).filter("foo >", 12); MyEntity e = q.get(); e = q.sort("foo").get(); for (MyEntity e : q)  print(e); List<MyEntity> entities = q.asList(); ------------------------------------------------- 生命週期方法註解: @PrePersist - 在save之前呼叫,可以返回一個DBObject以替代一個空值 @PostPersist - 在save之後呼叫 @PreLoad - 在將資料對映到POJO之前呼叫,DBObject作為引數傳入,你可以手動修改其中的值 @PostLoad - 在將資料對映到POJO之後呼叫 測試類例子: http://code.google.com/p/morphia/source/browse/trunk/morphia/src/test/java/com/google/code/morphia/TestDatastore.java //這個例子儲存前將最後登入時間更新為當前時間 class BankAccount {   @Id String id;   Date lastUpdated = new Date();   @PrePersist void prePersist() {lastUpdated = new Date();} } 下面的這個例子中,通過EntityListerners註解,將生命週期事件的實現放到外部類當中: @EntityListeners(BackAccountWatcher.class) class BankAccount {   @Id String id;   Date lastUpdated = new Date(); } class BankAccountWatcher{   @PrePersist void prePersist(BankAccount act) {act.lastUpdated = new Date();} } 注意:沒有delete操作相關的生命週期事件。 ------------------------------------------------- 類註解: ------------------------- @Entity 標記一個要被對映的類。此註解為可選,一般寫上沒壞處。 @Entity("collectionName") 指定對應的collection的名稱,這種情況POJO裡必須有無參構造方法 @Entity(noClassnameStored = true) 註解不要儲存類名。 預設是儲存類名的,因為可以把不同的類(特別是相互繼承的類)存入同一個collection,例如: @Entity("animals") abstract class Animal { String name; } @Entity("animals") Cat extends Animal { ... } @Entity("animals") Dog extends Animal { ... } @Entity(cap = @CappedAt(...)) 指定為Capped ------------------------- @Indexes 索引註解可以標記在類上,這樣就可以建立多列索引 @Entity @Indexes( @Index("user, -date") ) public class ChangeLog{ Date date; String user; Record changedRecord; } 注:"date"前面的負號表示日期降序排列,便於查詢最近的使用者 還可以新增多個多列索引: @Indexes({    @Index("user, -cs"),    @Index("changedRecord, -cs")}) 注:也可以(但不建議)在這裡定義單列索引,但最好還是註解在具體的列上 ------------------------------------------------- 欄位註解: ------------------------- @Id 標記欄位為資料庫主鍵 POJO裡的對應欄位可以是任意支援持久化的型別,例如int,uuid,Object 使用ObjectId可以實現自動生成,否則在每次儲存前需要指定此值 ------------------------- @Indexed  註解索引 每次呼叫datastore.ensureIndexes()時,這些索引就會被應用。 注:如果在一個已經存在資料和索引的系統上,呼叫datastore.ensureIndexes()方法,將不會產生任何操作,所以可以放心的呼叫。 @Indexed(value=IndexDirection.ASC, name="upc", unique=true, dropDups=true) value:索引方向,預設是IndexDirection.ASC,還可以是IndexDirection.DESC和IndexDirection.BOTH name:索引名,預設是mongoDB自動生成 unique:是否唯一索引,預設為flase dropDups:通知唯一索引靜默刪除已有的重複元素,只保留第一個,預設為false ------------------------- @Embedded 註解需要嵌入的物件(形成一個物件樹來讀取/寫入) 被嵌入的物件將巢狀在父物件裡,被存入同一個collection中,所以被嵌入的物件裡不允許出現@Id註解。 也可以指定被嵌入物件在mongoDB裡的屬性名: @Embedded("blog_comments") ------------------------- @Property POJO屬性註解 在POJO中,java原生型別和基本型別預設是不需要註解即可完成讀取和寫入的(除非註解了@Transient)。 mongoDB只支援四種資料型別:Integer,Long,Double,String morphia會自動對映java基本資料型別和String,這些型別的陣列,以及List,Set,Map到mongoDB中。 另外,以下物件也會被自動轉換和讀取/寫入: enum 轉為String儲存 java.util.Date 轉為毫秒數儲存 java.util.Locale 轉為String儲存 com.mongodb.DBRef com.mongodb.ObjectId 注:morphia預設使用POJO屬性名作為collection裡的欄位名,這個行為可以被覆蓋: @Property("my_integer") private int myInt; ------------------------- @Reference 引用註解,標記某一個欄位存在另一個collection中。 該註解有三種屬性: lazy:懶漢模式,被呼叫時才從資料庫獲取此欄位 ignoreMissing:讀取引用失敗時不產生異常 concreteClass:產生的例項的類型別 例子: @Entity public class BlogEntry { @Id private ObjectId id; @Reference private Author author;} @Entity public class Author { @Id private ObjectId id;} 注:被引用的物件必須存在於mongoDB中,然後才能儲存引用其的物件。 注:morphia預設使用POJO裡引用的屬性名作為collection裡的欄位名,這個行為可以被覆蓋: @Reference("blog_authors") private List<Author> authors; ------------------------- 集合類相關的屬性註解: morphia支援Collection(List,Set,Map): private Set<String> tags; private Map<String,Translation> translations; @Reference private List<Article> relatedArticles; 預設情況下,morphia讀取資料建立例項時會使用以下實現類: java.util.ArrayList java.util.HashSet java.util.HashMap 這個行為可以被覆蓋: @Property(concreteClass = java.util.TreeSet.class) private Set<String> tags; @Embedded(concreteClass = java.util.TreeMap.class) private Map<String,Translation> translations; @Reference(concreteClass = java.util.Vector.class) private List<Article> relatedArticles; ------------------------- @Transient 標記欄位被忽略,包括讀取/寫入 @Serialized 欄位被轉為二進位制後儲存 @NotSaved 欄位可以被讀取,但在寫入時忽略 @AlsoLoad 欄位可以被任何支援的名字所讀取 @Version 版本號標記,自動實現樂觀鎖,標記後修改操作時可能會丟擲ConcurrentModificationException ------------------------------------------------- Morphia的擴充套件: ------------------------- 校驗擴充套件ValidationExtension: 符合JSR303,可以直接呼叫Hibernate validation。 對JSR303 API的輕量級的包裝:new ValidationExtension(morphia); import org.hibernate.validator.constraints.Email; @Entity public class Userlike {         @Id ObjectId id;         @Email String email; } ------------------------- 日誌重定向擴充套件:SLF4JExtension 將morphia的執行日誌重定向到SLF4J中,引入morphia-logging-slf4j-0.99.jar,在系統啟動時執行一次: MorphiaLoggerFactory.registerLogger(SLF4JLoggerImplFactory.class); ------------------------------------------------- 手動對映物件到DBObjects(以便傳遞給底層的driver): ------------------------- 建立Morphia例項(建議只建立一次,然後重用它): Morphia morphia = new Morphia(); morphia.map(BlogEntry.class).map(Author.class); 每一個這樣手動對映的類都會被檢查,如果失敗會丟擲MappingException。 也可以讓morphia自動掃描某個包,並自動對映: morphia.mapPackage("my.package.with.only.mongo.entities"); ------------------------- 高階使用:從morphia裡直接呼叫底層的java driver儲存資料(手動): Morphia morphia = ...; Mongo mongo = ...; DB db = mongo.getDB("BlogSite"); //這是註解過的POJO BlogEntry blogEntry = ...; //讓morphia將POJO轉為java driver需要的DBObject DBObject blogEntryDbObj = morphia.toDBObject(blogEntry); //用java driver將其寫入mongodb db.getCollection("BlogEntries").save(blogEntryDbObj); ------------------------- 高階使用:從morphia裡直接呼叫底層的java driver讀取資料(手動): Morphia morphia = ...; Mongo mongo = ...; DB db = mongo.getDB("BlogSite"); //要讀取的ID String blogEntryId = ...; //呼叫java driver從mongdoDB取出一個DBObject BasicDBObject blogEntryDbObj = (BasicDBObject) db.getCollection("BlogEntries").findOne(new BasicDBObject("_id", new ObjectId(blogEntryId)); //讓morphia將DBObject轉為POJO BlogEntry blogEntry = morphia.fromDBObject(BlogEntry.class, blogEntryDbObj); ------------------------------------------------- DAO層的封裝: morphia已經提供了一個DAO介面和一個BasicDAO類。 我們只要繼承BasicDAO類,覆蓋其中的構造方法,將mongo和morphia物件傳入即可: public class BlogEntryDAO extends BasicDAO<BlogEntry, ObjectId> {     public BlogEntryDAO( Morphia morphia, Mongo mongo,String dbName) {         super(mongo, morphia, dbName);     } } 然後就可以實現大部分操作: BlogEntryDAO blogEntryDAO = new BlogEntryDAO(...); ObjectId  blogEntryId = ...; BlogEntry myBlogEntry = blogEntryDAO.get(blogEntryId);//查詢 myBlogEntry.setTitle("My Blog Entry"); blogEntryDAO.save(myBlogEntry);//儲存 blogEntryDAO.deleteById(myBlogEntry.getId());//刪除 然後還需要自定義一些查詢方法(注意這裡用了正則匹配文章標題): public List<BlogEntry> findByTitle( String title ) {     Pattern regExp = Pattern.compile(title + ".*", Pattern.CASE_INSENSITIVE);     return ds.find(entityClazz).filter("title", regExp).sort("title").asList(); } 星遊注:話說,加入DAO層不就是為了與morphia解耦麼?現在DAO層的父類本身就在morphia包裡,“這不科學呀。。。” 建議參照其BasicDAO,自己寫一個,這才實現了與持久層解耦: http://code.google.com/p/morphia/source/browse/trunk/morphia/src/main/java/com/google/code/morphia/dao/BasicDAO.java