快速開始:
@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創(chuàng)建后index和capped collections的初始化:
ds.ensureIndexes(); ///creates all defined with @Indexed
ds.ensureCaps(); //creates all collections for @Entity(cap=@CappedAt(...))
這兩個(gè)方法應(yīng)該在你把實(shí)體注冊到morphia之后(morphia.map(Hotel.class).map(Address.class))來調(diào)用,它默認(rèn)會同步的創(chuàng)建index和capped collections,這可以在每次系統(tǒng)啟動(dòng)時(shí)確保索引存在(包括第一次運(yùn)行的時(shí)候)。
查詢一條記錄:
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方法:其實(shí)就是find方法的快捷方式,根據(jù)ID返回一個(gè)實(shí)體,或者返回null。
Hotel hotel = ds.get(Hotel.class, hotelId);
-------------------------
find方法:會返回一個(gè)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方法:根據(jù)一個(gè)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方法:更新部分信息時(shí)比整體save一次要高效的多,例如更新用戶的最后登錄時(shí)間:
@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;
}
}
有關(guān)update方法的更多高級操作:
http://code.google.com/p/morphia/wiki/Updating
-------------------------------------------------
Query接口介紹:
可以添加查詢條件,排序,限定返回結(jié)果條數(shù)和位置。實(shí)現(xiàn)了QueryResults接口。
-------------------------
Filter方法:
Query q = ds.createQuery(MyEntity.class).filter("foo >", 12);
第一個(gè)參數(shù)是屬性名和比較符,第二個(gè)參數(shù)是比較值。
多個(gè)fileter直接的關(guān)系是“與(and)”
Query q = ds.createQuery(MyEntity.class).filter("foo >", 12).filter("foo <", 30);
可用操作符列表:
["=", "==","!=", "<>", ">", "<", ">=", "<=", "in", "nin", "all", "size", "exists"]
-------------------------
Fluent接口:
與Fileter接口差不多,英文呢方法名,更符合使用習(xí)慣:
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,可以用“點(diǎn)號”指定屬性。
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:如果查詢時(shí)屬性名找不到,則會拋出異常。如果用到“點(diǎn)號”指定屬性,則表達(dá)式每一部分都必須匹配類的結(jié)構(gòu)圖。
如果服務(wù)器可以強(qiáng)制轉(zhuǎn)換數(shù)據(jù),則數(shù)據(jù)類型的錯(cuò)誤會被作為警告記錄下來。
上述校驗(yàn)特性可以在任何一個(gè)查詢或查詢的一部分處被關(guān)閉:
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)方法:要求服務(wù)器跳過一部分記錄,這么做效率不如使用一些屬性的range filter,比如pagination。
Query q = ds.createQuery(MyEntity.class).filter("foo >", 12).offset(1000);
-------------------------
只返回指定的屬性:這么做會導(dǎo)致不完整的對象,小心使用。
MyEntity e = ds.createQuery(MyEntity.class).retrievedFields(true, "foo").get();
val = e.getFoo(); // 僅返回這個(gè)字段
MyEntity e = ds.createQuery(MyEntity.class).retrievedFields(false, "foo").get();
val = e.getFoo(); // 僅不返回這個(gè)字段
指定字段名的參數(shù)可以是字符串list或者數(shù)組:
MyEntity e = ds.createQuery(MyEntity.class).retrievedFields(true, "foo", "bar").get();
-------------------------
取數(shù)據(jù):
使用QueryResults接口的方法即可取到數(shù)據(jù),這些方法不會影響Query對象本身(返回的是副本),所以可以反復(fù)調(diào)用。
get() 使用limit(1),返回一條記錄
asList() 返回所有記錄,大數(shù)據(jù)量時(shí)可能較慢
fetch() 明確要求返回一個(gè)可迭代的對象
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之前調(diào)用,可以返回一個(gè)DBObject以替代一個(gè)空值
@PostPersist - 在save之后調(diào)用
@PreLoad - 在將數(shù)據(jù)映射到POJO之前調(diào)用,DBObject作為參數(shù)傳入,你可以手動(dòng)修改其中的值
@PostLoad - 在將數(shù)據(jù)映射到POJO之后調(diào)用
測試類例子:
http://code.google.com/p/morphia/source/browse/trunk/morphia/src/test/java/com/google/code/morphia/TestDatastore.java
//這個(gè)例子保存前將最后登錄時(shí)間更新為當(dāng)前時(shí)間
class BankAccount {
@Id String id;
Date lastUpdated = new Date();
@PrePersist void prePersist() {lastUpdated = new Date();}
}
下面的這個(gè)例子中,通過EntityListerners注解,將生命周期事件的實(shí)現(xiàn)放到外部類當(dāng)中:
@EntityListeners(BackAccountWatcher.class)
class BankAccount {
@Id String id;
Date lastUpdated = new Date();
}
class BankAccountWatcher{
@PrePersist void prePersist(BankAccount act) {act.lastUpdated = new Date();}
}
注意:沒有delete操作相關(guān)的生命周期事件。
-------------------------------------------------
類注解:
-------------------------
@Entity
標(biāo)記一個(gè)要被映射的類。此注解為可選,一般寫上沒壞處。
@Entity("collectionName")
指定對應(yīng)的collection的名稱,這種情況POJO里必須有無參構(gòu)造方法
@Entity(noClassnameStored = true)
注解不要存儲類名。
默認(rèn)是存儲類名的,因?yàn)榭梢园巡煌念?特別是相互繼承的類)存入同一個(gè)collection,例如:
@Entity("animals") abstract class Animal { String name; }
@Entity("animals") Cat extends Animal { ... }
@Entity("animals") Dog extends Animal { ... }
@Entity(cap = @CappedAt(...)) 指定為Capped
-------------------------
@Indexes 索引注解可以標(biāo)記在類上,這樣就可以建立多列索引
@Entity
@Indexes( @Index("user, -date") )
public class ChangeLog{
Date date;
String user;
Record changedRecord;
}
注:"date"前面的負(fù)號表示日期降序排列,便于查找最近的用戶
還可以添加多個(gè)多列索引:
@Indexes({
@Index("user, -cs"),
@Index("changedRecord, -cs")})
注:也可以(但不建議)在這里定義單列索引,但最好還是注解在具體的列上
-------------------------------------------------
字段注解:
-------------------------
@Id 標(biāo)記字段為數(shù)據(jù)庫主鍵
POJO里的對應(yīng)字段可以是任意支持持久化的類型,例如int,uuid,Object
使用ObjectId可以實(shí)現(xiàn)自動(dòng)生成,否則在每次存儲前需要指定此值
-------------------------
@Indexed 注解索引
每次調(diào)用datastore.ensureIndexes()時(shí),這些索引就會被應(yīng)用。
注:如果在一個(gè)已經(jīng)存在數(shù)據(jù)和索引的系統(tǒng)上,調(diào)用datastore.ensureIndexes()方法,將不會產(chǎn)生任何操作,所以可以放心的調(diào)用。
@Indexed(value=IndexDirection.ASC, name="upc", unique=true, dropDups=true)
value:索引方向,默認(rèn)是IndexDirection.ASC,還可以是IndexDirection.DESC和IndexDirection.BOTH
name:索引名,默認(rèn)是mongoDB自動(dòng)生成
unique:是否唯一索引,默認(rèn)為flase
dropDups:通知唯一索引靜默刪除已有的重復(fù)元素,只保留第一個(gè),默認(rèn)為false
-------------------------
@Embedded 注解需要嵌入的對象(形成一個(gè)對象樹來讀取/寫入)
被嵌入的對象將嵌套在父對象里,被存入同一個(gè)collection中,所以被嵌入的對象里不允許出現(xiàn)@Id注解。
也可以指定被嵌入對象在mongoDB里的屬性名:
@Embedded("blog_comments")
-------------------------
@Property POJO屬性注解
在POJO中,java原生類型和基本類型默認(rèn)是不需要注解即可完成讀取和寫入的(除非注解了@Transient)。
mongoDB只支持四種數(shù)據(jù)類型:Integer,Long,Double,String
morphia會自動(dòng)映射java基本數(shù)據(jù)類型和String,這些類型的數(shù)組,以及List,Set,Map到mongoDB中。
另外,以下對象也會被自動(dòng)轉(zhuǎn)換和讀取/寫入:
enum 轉(zhuǎn)為String存儲
java.util.Date 轉(zhuǎn)為毫秒數(shù)存儲
java.util.Locale 轉(zhuǎn)為String存儲
com.mongodb.DBRef
com.mongodb.ObjectId
注:morphia默認(rèn)使用POJO屬性名作為collection里的字段名,這個(gè)行為可以被覆蓋:
@Property("my_integer")
private int myInt;
-------------------------
@Reference 引用注解,標(biāo)記某一個(gè)字段存在另一個(gè)collection中。
該注解有三種屬性:
lazy:懶漢模式,被調(diào)用時(shí)才從數(shù)據(jù)庫獲取此字段
ignoreMissing:讀取引用失敗時(shí)不產(chǎn)生異常
concreteClass:產(chǎn)生的實(shí)例的類類型
例子:
@Entity public class BlogEntry { @Id private ObjectId id; @Reference private Author author;}
@Entity public class Author { @Id private ObjectId id;}
注:被引用的對象必須存在于mongoDB中,然后才能存儲引用其的對象。
注:morphia默認(rèn)使用POJO里引用的屬性名作為collection里的字段名,這個(gè)行為可以被覆蓋:
@Reference("blog_authors")
private List<Author> authors;
-------------------------
集合類相關(guān)的屬性注解:
morphia支持Collection(List,Set,Map):
private Set<String> tags;
private Map<String,Translation> translations;
@Reference private List<Article> relatedArticles;
默認(rèn)情況下,morphia讀取數(shù)據(jù)創(chuàng)建實(shí)例時(shí)會使用以下實(shí)現(xiàn)類:
java.util.ArrayList
java.util.HashSet
java.util.HashMap
這個(gè)行為可以被覆蓋:
@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 標(biāo)記字段被忽略,包括讀取/寫入
@Serialized 字段被轉(zhuǎn)為二進(jìn)制后儲存
@NotSaved 字段可以被讀取,但在寫入時(shí)忽略
@AlsoLoad 字段可以被任何支持的名字所讀取
@Version 版本號標(biāo)記,自動(dòng)實(shí)現(xiàn)樂觀鎖,標(biāo)記后修改操作時(shí)可能會拋出ConcurrentModificationException
-------------------------------------------------
Morphia的擴(kuò)展:
-------------------------
校驗(yàn)擴(kuò)展ValidationExtension:
符合JSR303,可以直接調(diào)用Hibernate validation。
對JSR303 API的輕量級的包裝:new ValidationExtension(morphia);
import org.hibernate.validator.constraints.Email;
@Entity
public class Userlike {
@Id ObjectId id;
@Email String email;
}
-------------------------
日志重定向擴(kuò)展:SLF4JExtension
將morphia的運(yùn)行日志重定向到SLF4J中,引入morphia-logging-slf4j-0.99.jar,在系統(tǒng)啟動(dòng)時(shí)執(zhí)行一次:
MorphiaLoggerFactory.registerLogger(SLF4JLoggerImplFactory.class);
-------------------------------------------------
手動(dòng)映射對象到DBObjects(以便傳遞給底層的driver):
-------------------------
創(chuàng)建Morphia實(shí)例(建議只創(chuàng)建一次,然后重用它):
Morphia morphia = new Morphia();
morphia.map(BlogEntry.class).map(Author.class);
每一個(gè)這樣手動(dòng)映射的類都會被檢查,如果失敗會拋出MappingException。
也可以讓morphia自動(dòng)掃描某個(gè)包,并自動(dòng)映射:
morphia.mapPackage("my.package.with.only.mongo.entities");
-------------------------
高級使用:從morphia里直接調(diào)用底層的java driver存儲數(shù)據(jù)(手動(dòng)):
Morphia morphia = ...;
Mongo mongo = ...;
DB db = mongo.getDB("BlogSite");
//這是注解過的POJO
BlogEntry blogEntry = ...;
//讓morphia將POJO轉(zhuǎn)為java driver需要的DBObject
DBObject blogEntryDbObj = morphia.toDBObject(blogEntry);
//用java driver將其寫入mongodb
db.getCollection("BlogEntries").save(blogEntryDbObj);
-------------------------
高級使用:從morphia里直接調(diào)用底層的java driver讀取數(shù)據(jù)(手動(dòng)):
Morphia morphia = ...;
Mongo mongo = ...;
DB db = mongo.getDB("BlogSite");
//要讀取的ID
String blogEntryId = ...;
//調(diào)用java driver從mongdoDB取出一個(gè)DBObject
BasicDBObject blogEntryDbObj = (BasicDBObject) db.getCollection("BlogEntries").findOne(new BasicDBObject("_id", new ObjectId(blogEntryId));
//讓morphia將DBObject轉(zhuǎn)為POJO
BlogEntry blogEntry = morphia.fromDBObject(BlogEntry.class, blogEntryDbObj);
-------------------------------------------------
DAO層的封裝:
morphia已經(jīng)提供了一個(gè)DAO接口和一個(gè)BasicDAO類。
我們只要繼承BasicDAO類,覆蓋其中的構(gòu)造方法,將mongo和morphia對象傳入即可:
public class BlogEntryDAO extends BasicDAO<BlogEntry, ObjectId> {
public BlogEntryDAO( Morphia morphia, Mongo mongo,String dbName) {
super(mongo, morphia, dbName);
}
}
然后就可以實(shí)現(xiàn)大部分操作:
BlogEntryDAO blogEntryDAO = new BlogEntryDAO(...);
ObjectId blogEntryId = ...;
BlogEntry myBlogEntry = blogEntryDAO.get(blogEntryId);//查詢
myBlogEntry.setTitle("My Blog Entry");
blogEntryDAO.save(myBlogEntry);//保存
blogEntryDAO.deleteById(myBlogEntry.getId());//刪除
然后還需要自定義一些查詢方法(注意這里用了正則匹配文章標(biāo)題):
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解耦么?現(xiàn)在DAO層的父類本身就在morphia包里,“這不科學(xué)呀。。。”
建議參照其BasicDAO,自己寫一個(gè),這才實(shí)現(xiàn)了與持久層解耦:
http://code.google.com/p/morphia/source/browse/trunk/morphia/src/main/java/com/google/code/morphia/dao/BasicDAO.java