上次的Blog中写到要写一篇关于mongodb的ORM工具的文章。上个月程序基本写完,这回把Blog补上:)
本文主要介绍mongodb的ORM工具morphia的使用。
首先介绍抽象类AbstractMongoDAO,里面包含有Morphia的初始化代码,因此继承了BasicDAO。AbstractMongoDAO.java:
public class AbstractMongoDAO<T, K> extends BasicDAO<T, K> {
protected static final Morphia morphia = createMorphia();
protected static final Mongo mongoInstance = createMongoInstance();
protected static final Datastore morphiaDs = createDataStore();
protected AbstractMongoDAO(Class<T> entityClass, Datastore ds) {
super(entityClass, ds);
}
private static Morphia createMorphia() {
return new Morphia();
}
private static Datastore createDataStore() {
morphia.mapPackage("mongodb.orm.model", true);//映射model的包路径
if (DatabaseProject.DB_CONFIG.containsKey("mongodb.username")) {
return morphia.createDatastore(mongoInstance, DatabaseProject.DB_CONFIG.getString("mongodb.db")
, DatabaseProject.DB_CONFIG.getString("mongodb.username"), DatabaseProject.DB_CONFIG.getString("mongodb.password").toCharArray());
}
Datastore ds = morphia.createDatastore(mongoInstance, DatabaseProject.DB_CONFIG.getString("mongodb.db"));
ds.ensureIndexes();
return ds;
}
protected AbstractMongoDAO(Class<T> entityClass, Mongo mongo, Morphia morphia, String dbName) {
super(entityClass, mongo, morphia, dbName);
}
private static Mongo createMongoInstance() {
MongoOptions mo = new MongoOptions();
mo.socketKeepAlive=true;
mo.autoConnectRetry = true;
mo.maxAutoConnectRetryTime=10;
mo.connectionsPerHost = 40;
mo.connectTimeout = 20 * 1000;
mo.socketTimeout = 60 * 1000;
try {
if (DatabaseProject.DB_CONFIG.containsKey("mongodb.ips")) {
return new Mongo(getServerAddrsFromConf("mongodb"),mo);
}
return new Mongo(new ServerAddress(DatabaseProject.DB_CONFIG.getString("mongodb.ip"), DatabaseProject.DB_CONFIG.getInt("mongodb.port")),mo);
} catch (Throwable e) {
DatabaseProject.LOGGER.error("Failed to init mongodb", e);
throw new ExceptionInInitializerError(e);
}
}
@SuppressWarnings("unchecked")
public static List<ServerAddress> getServerAddrsFromConf(String confKeyPre) throws NumberFormatException, UnknownHostException {
ArrayList<ServerAddress> res = new ArrayList<ServerAddress>();
List<Object> ips = DatabaseProject.DB_CONFIG.getList(confKeyPre + ".ips");
List<Object> ports = DatabaseProject.DB_CONFIG.getList(confKeyPre + ".port");
if (ports.size() < ips.size()) {
int defPort = 0;
if (ports.size() != 0) defPort = Integer.parseInt(ports.get(0).toString());
for (int i = 0; i < ips.size(); ++i) {
if (defPort == 0) res.add(new ServerAddress(ips.get(i).toString()));
else res.add(new ServerAddress(ips.get(i).toString(), defPort));
}
} else {
for (int i = 0; i < ips.size(); ++i) {
res.add(new ServerAddress(ips.get(i).toString(),
Integer.parseInt(ports.get(i).toString())));
}
}
return res;
}
public static Mongo getMongoInstance() {
return mongoInstance;
}
public static DB getDB() {
return morphiaDs.getDB();
}
public static Morphia getMorphia() {
return morphia;
}
public Iterable<Key<T>> insert(Iterable<T> entries, WriteConcern wc) {
return ds.insert(entries, wc);
}
public Iterable<Key<T>> insert(Iterable<T> entries) {
return ds.insert(entries);
}
}
注意createMongoInstance方法(上一篇介绍mongo java driver的文章中也有提到),主要是实例化一个mongo实例。
接下来是createDataStore方法,该方法通过morphia对象及使用mongo实例创建一个Datastore对象。其中morphia.mapPackage("mongodb.orm.model", true);是将mongodb.orm.model包路径下的所有类都进行关系对象映射。
下面是Model类:
/**
* @Entity
* value代表生成的集合名称、不写默认为类名(此处设置为与类名相同)
* noClassnameStored如果设置为false,文档中会生成一列className保存类名("className":"mongodb.orm.model.OrmColl")
*/
@Entity(value="OrmColl",noClassnameStored = true)
/**
* @Indexes
* 创建复合索引(用,分隔).
* 此处设置userId升序,name降序.
* unique:设置为唯一,无法插入重复值.
* dropDups:当为某个字段创建唯一索引时,删除其他相同值的记录。只保留第一条记录.
* true-删除重复,false-不删除重复(当有重复值时唯一索引创建失败),默认为false.
* @Indexes({@Index(...),@Index(...)})多个索引加{}并用,分隔.
* Datastore的ensureIndexes调用时才会创建索引.
*/
@Indexes(@Index(value="userId,-name",unique=true,dropDups=false))
public class OrmColl{
@Id
private ObjectId id;//Id
private long userId;
private String name;
private int age;
private Date createDate = new Date();
@Property("male")//该属性存储到collection中元素名指定为male
private boolean sex;
/**
* concreteClass:为接口指名实现类,默认
* java.util.ArrayList for List
* java.util.HashSet for Set
* java.util.HashMap for Map
* 此处为List指明实现类为java.util.Vector
*/
@Property(concreteClass=java.util.Vector.class)
public List<String> list;//添加范型指名list类型,否则出现警告
@Embedded(value="embedded_doc")
private InnerDocument doc;//嵌入文档,默认为属性名.此处指定了元素名称.
@Transient
private int transientValue;//设置为@Transient不会被持久化
@Serialized
private int serializedValue;//序列化
@Version
Long version;//乐观锁
@Reference
private RefOrmColl refOrmColl;//自动生成名为refOrmColl的内嵌文档,包含"$ref"和"$id"属性,指明引用集合名及@Id属性值.
@Reference
private List<RefOrmColl> refList;//自动生成名为refList的数组,数组的每个元素都是内嵌文档,文档形式同refOrmColl.
public OrmColl(){}
@PrePersist
private void prePersist() {createDate = new Date();}
//setter & getter
}
补充@Entity(concern = "SAFE")属性(粘贴自文档):
NONE: No exceptions are raised, even for network issues
NORMAL: Exceptions are raised for network issues, but not server errors
SAFE: Exceptions are raised for network issues, and server errors; waits on a server for the write operation
FSYNC_SAFE: Exceptions are raised for network issues, and server errors and the write operation waits for the server to flush the data to disk
REPLICAS_SAFE: Exceptions are raised for network issues, and server errors; waits for at least 2 servers for the write operation
其中version属性不需要getter和setter方法,内嵌对象为InnerDocument。代码如下:
/**
* 被@Embedded注解的类不允许有@Id
*/
@Embedded
public class InnerDocument {
private String type;
private long longValue;
private Date createDate;
public InnerDocument(){}
@PrePersist
private void prePersist() {createDate = new Date();}
//setter & getter...
}
引用对象为RefOrmColl.java,格式如下:
@Entity(value="RefOrmColl",noClassnameStored = true)
public class RefOrmColl {
@Id
private ObjectId id;//被关联的集合一定要包含@Id属性
private long longValue;
private String type;
private Date date;
@PrePersist
private void prePersist() {date = new Date();}
//setter&getter...
}
在OrmColl的集合中自动生成RefOrmColl类型对象属性名的内嵌文档或数组(本例中是refOrmColl和refList),包含"$ref"和"$id"属性,以指明引用集合名及@Id属性值.
DAO实现类:
OrmCollDao.java如下:
public class OrmCollDao extends AbstractMongoDAO<OrmColl,ObjectId>{//OrmColl主键是OjectId
private static OrmCollDao childDaoInstance = createDAOInstance();
protected static DefaultMorphiaMongoDAO<RefOrmColl> refDao = DefaultMorphiaMongoDAO.getInstance(RefOrmColl.class);
private static OrmCollDao createDAOInstance(){
return new OrmCollDao(OrmColl.class,morphiaDs);
}
protected OrmCollDao(Class<OrmColl> entityClass,Datastore ds){
super(entityClass,ds);
}
protected OrmCollDao(Class<OrmColl> entityClass, Mongo mongo, Morphia morphia, String dbName){
super(entityClass,mongo,morphia,dbName);
}
public static OrmCollDao getInstance(){
return childDaoInstance;
}
/**
* 数据初始化
*/
public static void insertRefOrmColl(){
List list = new ArrayList();
for(int i=0;i<100;i++){
RefOrmColl roc = new RefOrmColl();
roc.setLongValue(i);
roc.setType("type"+i);
list.add(roc);
}
//insert可以批量插入list,save只能单个插入,但save可以更新.
refDao.insert(list);
}
/**
* 数据初始化
*/
public static void insertOrmColl(){
//List<OrmColl> ormList = new ArrayList<OrmColl>();
//查询一个关联集合的List
Query<RefOrmColl> listQuery = refDao.createQuery().field("longValue").lessThan(10);
List<RefOrmColl> refList = refDao.find(listQuery).asList();
for(int i=0;i<100;i++){
OrmColl oc = new OrmColl();
oc.setAge(1+i);
oc.setName("name"+i);
oc.setUserId(10000+i);
oc.setSex(true);
oc.setTransientValue(12345);
oc.setSerializedValue(1234567);
InnerDocument doc = new InnerDocument();//内嵌文档
doc.setType("type_str");
doc.setLongValue(1986L);
oc.setDoc(doc);
Query<RefOrmColl> query = refDao.createQuery().field("longValue").equal(i);
RefOrmColl refColl = refDao.findOne(query);//查询关联集合的文档
if(refColl!=null){
//设置关联
oc.setRefOrmColl(refColl);
oc.setRefList(refList);
}
OrmCollDao.getInstance().save(oc);//使用save方法会自动生成@version属性
//ormList.add(oc);
}
//OrmCollDao.getInstance().insert(ormList);//使用批量插入,文档中不会自动生成@version属性.
}
/**
* 根据id加载对象
*/
public static void get(String _id){
OrmCollDao dao = OrmCollDao.getInstance();
OrmColl obj = dao.get(new ObjectId(_id));
displayObj(obj);
}
/**
* filter查询方式(直接使用 > < >= <= = exists等表达式)
*/
public static void findByFilter(){
OrmCollDao dao = OrmCollDao.getInstance();
//filter之间以and连接
Query query = dao.createQuery().filter("age >",1);
query.filter("userId <=", 10010);
query.filter("male =", true);
query.filter("embedded_doc.type =", "type_str");
query.filter("refList exists ", true);
List<OrmColl> list = dao.find(query).asList();
System.out.println("list.size:"+list.size());
for(int i=0;i<list.size();i++){
displayObj(list.get(i));
}
}
/**
* Fluent查询方式(使用morphia封装的方法greaterThan、lessThanOrEq、equal、exists)
*/
public static void findByFluent(){
OrmCollDao dao = OrmCollDao.getInstance();
Query query = dao.createQuery();
//不同条件之间为and
query.field("age").greaterThan(1);
query.field("userId").lessThanOrEq(10010);
query.field("male").equal(true);
query.field("embedded_doc.type").equal("type_str");
query.field("refList").exists();//doesNotExist
List<OrmColl> list = dao.find(query).asList();
System.out.println("list.size:"+list.size());
for(int i=0;i<list.size();i++){
displayObj(list.get(i));
}
}
/**
* Fluent查询方式(根据id查询文档)
*/
public static void findById(String _id){
OrmCollDao dao = OrmCollDao.getInstance();
Query query = dao.createQuery();
query.field(Mapper.ID_KEY).equal(new ObjectId(_id));//Mapper.ID_KEY == "_id"
OrmColl obj = dao.findOne(query);
displayObj(obj);
}
/**
* Fluent查询方式 ,OR条件查询
*/
public static void orQuery(){
OrmCollDao dao = OrmCollDao.getInstance();
Query<OrmColl> query = dao.createQuery();
query.or(
query.criteria("userId").equal(10010),
query.criteria("age").greaterThan(1)
);
List<OrmColl> list = dao.find(query).asList();
System.out.println("list.size:"+list.size());
for(int i=0;i<list.size();i++){
displayObj(list.get(i));
}
}
/**
* Count求和
*/
public static void getCount(){
OrmCollDao dao = OrmCollDao.getInstance();
Query query = dao.createQuery().field("userId").greaterThanOrEq(10095);
long count = dao.count(query);
System.out.println(count);
}
/**
* 更新操作
*/
public static void updateEntity(String _id){
OrmCollDao dao = OrmCollDao.getInstance();
UpdateOperations<OrmColl> ops = dao.createUpdateOperations().set("createDate",new Date()).set("serializedValue",12345);
UpdateResults<OrmColl> ur = dao.update(dao.createQuery().field(Mapper.ID_KEY).equal(new ObjectId(_id)),ops);
System.out.println(ur.getInsertedCount());
}
/**
* 对象转换并打印对象属性
*/
public static void displayObj(OrmColl obj){
Morphia morphia = getMorphia();
DBObject object = morphia.toDBObject(obj);//将对象转换成DBObject.
System.out.println(object.toString());
}
/**
* Mapper and EntityCache
*/
public static void mapper(String _id){
OrmCollDao dao = OrmCollDao.getInstance();
OrmColl oc = dao.get(new ObjectId(_id));
Mapper mapper = morphia.getMapper();
if(mapper!=null && morphia.isMapped(OrmColl.class)){
System.out.println("getCollectionName:"+mapper.getCollectionName(oc));
System.out.println("Id:"+mapper.getId(oc));
System.out.println("Key:"+mapper.getKey(oc));
System.out.println("keyToRef:"+mapper.keyToRef(mapper.getKey(oc)));
System.out.println("Options:"+mapper.getOptions());
}
EntityCache cache = mapper.createEntityCache();
cache.putEntity(mapper.getKey(oc),oc);
System.out.println("cache obj 1: "+cache.getEntity(mapper.getKey(oc)).getName());
System.out.println("cache obj 2: "+cache.getEntity(mapper.getKey(oc)).getName());
System.out.println("cache obj exists: "+cache.exists(mapper.getKey(oc)));
EntityCacheStatistics ecs = cache.stats();
System.out.println("EntityCacheStatistics1: "+ecs.toString());//cache统计1: 1 entities,3 hits
cache.putProxy(mapper.getKey(oc),oc);
System.out.println("cache proxy: "+cache.getProxy(mapper.getKey(oc)).getName());
ecs = cache.stats();
System.out.println("EntityCacheStatistics2: "+ecs.toString());//cache统计2: 2 entities,4 hits
//所有已经映射的class
for (MappedClass mc : mapper.getMappedClasses()) {
System.out.println("getMappedClasses: " + mc);
}
for (EntityInterceptor ei : mapper.getInterceptors()) {
System.out.println("EntityInterceptor: " + ei);
}
}
}
以上是针对OrmColl的集合建立的DAO,由于使用了泛型,因此find、update等操作都针对OrmColl,所以如果你需要在DAO中同时对其它集合进行操作的话,可以使用以下通用的DAO,DefaultMorphiaMongoDAO.java:
public class DefaultMorphiaMongoDAO<T> extends AbstractMongoDAO<T, ObjectId> {
private static final Map<String,DefaultMorphiaMongoDAO> _instanceMap = new ConcurrentHashMap<String,DefaultMorphiaMongoDAO>();
protected DefaultMorphiaMongoDAO(Class<T> entityClass, Datastore ds) {
super(entityClass,ds);
this.ensureIndexes();
}
public static DB getDB(){
return morphiaDs.getDB();
}
public static <T> DefaultMorphiaMongoDAO<T> getInstance(Class<T> clazz){
DefaultMorphiaMongoDAO inst = _instanceMap.get(clazz.getSimpleName());
if (inst==null) {
inst = new DefaultMorphiaMongoDAO<T>(clazz,morphiaDs);
_instanceMap.put(clazz.getSimpleName(),inst);
}
return inst;
}
}
通过泛型,你就可以用该DAO对任何Collection进行操作了,(注意,此处代码,<T, ObjectId>可以支持不同的Key类型,不一定是ObjectId)。
至此,Morphia的学习算是告一段落了。
其实在遇到很多问题的时候要多看文档,下面列出一些Morphia的参考资料,希望对读者有所帮助:
1.morphia官方用户手册: http://code.google.com/p/morphia/wiki/GettingStarted
2.morphia在线API文档地址:http://morphia.googlecode.com/svn/site/morphia/apidocs/index.html