您的位置:首页 > 数据库 > Mongodb

浅谈spring模板化处理风格操纵mongodb的使用

2016-07-17 15:24 260 查看

在spring已经成为Java web开发者必学技能的今天,适应spring编程风格将有助于快速理解和学习spring相关项目,避开aop和ioc不谈,比如工厂方法模式的身影随处可见,今天讨论的是spring 访问mongo的API模板,这种模板化的处理方式,也是spring常见的,以下是几个例子:

1.spring-ldap-core中的ldapTemplate

2.spring-jdbc中的jdbcTemplate

3.mybatis-spring中的sqlSessionTemplate

4.spring-data-mongodb中的MongoTemplate

,等等吧,最近在做关于mongo的项目,所以讲讲MongoTemplate,本文的代码是基于

spring-data-commons-1.10.0.RELEASE.jar

spring-data-mongodb-1.7.0.RELEASE.jar

mongo-java-driver-3.2.2.jar

当然还会有spring环境的包,这不是本文的重点,所以忽略。

一、查看源码了解MongoTemplate

继续查看MongoOperations和ApplicationContextAware发现,MongoTemplate中的所有对mongo数据操纵的方法都是来自于接口MongoOperations:



 至此,我们基本了解了MongoTemplate,我们已经迫切需要使用它了,那我们必须实例化它,所以有必要认识一下它的构造子:



 可以看到,有四个构造函数可用,前俩个源码如下

 

public MongoTemplate(Mongo mongo, String databaseName) {
this(new SimpleMongoDbFactory(mongo, databaseName), null);
}
public MongoTemplate(Mongo mongo, String databaseName, UserCredentials userCredentials) {
this(new SimpleMongoDbFactory(mongo, databaseName, userCredentials));
}
 但是SimpleMongoDbFactory(mongo, databaseName)已经是被标记为废弃的方法,所以不能用它们去实例化MongoTemplate,再看剩下两个构造子的源码:
public MongoTemplate(MongoDbFactory mongoDbFactory) {
this(mongoDbFactory, null);
}
public MongoTemplate(MongoDbFactory mongoDbFactory, MongoConverter mongoConverter) {

Assert.notNull(mongoDbFactory);

this.mongoDbFactory = mongoDbFactory;
this.exceptionTranslator = mongoDbFactory.getExceptionTranslator();
this.mongoConverter = mongoConverter == null ? getDefaultMongoConverter(mongoDbFactory) : mongoConverter;
this.queryMapper = new QueryMapper(this.mongoConverter);
this.updateMapper = new UpdateMapper(this.mongoConverter);

// We always have a mapping context in the converter, whether it's a simple one or not
mappingContext = this.mongoConverter.getMappingContext();
// We create indexes based on mapping events
if (null != mappingContext && mappingContext instanceof MongoMappingContext) {
indexCreator = new MongoPersistentEntityIndexCreator((MongoMappingContext) mappingContext, mongoDbFactory);
eventPublisher = new MongoMappingEventPublisher(indexCreator);
if (mappingContext instanceof ApplicationEventPublisherAware) {
((ApplicationEventPublisherAware) mappingContext).setApplicationEventPublisher(eventPublisher);
}
}
}
 通过上述源码可以清晰的看到这两个方法有个共同参数MongoDbFactory,第二个参数MongoConverter作用是:“Central Mongo specific converter interface which combines {@link MongoWriter} and {@link MongoReader}”(源码注释),所以我们决定使用第三个构造子,即通过MongoDbFactory对象生MongoTemplate实例,那又需要我们查看MongoDbFactory的构造子,最终我们可以通过host,port,username,password,dbname构造一个mongo实例,从而通过该实例构造一个MongoDbFactory对象,最终可以使用其成功实例化MongoTemplate,以下是在spring配置文件中的配置:
<!-- 加载mongodb的属性配置文件 -->
<context:property-placeholder location="classpath:/mongo/mongodb.properties" />
<!-- 构造MongoDbFactory对象 -->
<mongo:db-factory id="mongoDbFactory" host="${mongo.host}"
port="${mongo.port}" dbname="${mongo.dbname}" username="${mongo.username}"
password="${mongo.password}" />
<!-- 构造MongoTemplate -->
<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory" />
</bean>
 这样,我们需要的MongoTemplate实例会随着spring容器的启动而生成。下面我们要讨论的是基于该实例进行mongo的数据操纵(MongoDB的安装等环境准备工作省略)。 二、基于MongoTemplate对象的数据操纵 前面已经讨论过,基于MongoTemplate的数据从操纵实际上是接口MongoOperations中方法的实现和重载。本文只讨论CRUD四中常见操作: 1.查询 mongo中支持对数据的排序和分页,如下是笔者对分页查询的实现:
/**
* 查询分页数据
* @author young
* @param QueryEntity
*            查询条件分装实体
* @param entityClass
*            确定集合名称的实体类
*/
public <T> List<T> queryPageData(QueryEntity queryEntity, Class<T> entityClass) {
Assert.notNull(entityClass, "entityClass must not be null");
if (!hasCollection(entityClass)) {
logger.info("collection  determied by entityClass not be found ");
return null;
}
Query query = QueryEntityConverter.getQueryStataments(queryEntity);
List<T> listResult = getMongoTemplate().find(query, entityClass);
return listResult;
}

/**
* 查询分页数据
* @author young
* @param queryEntity
*            查询条件分装实体
* @param collectionName
*            集合名称
* @return
*/
public List<? extends Object> queryPageData(QueryEntity queryEntity, String collectionName) {
Assert.notNull(collectionName, "collection name must not null");
if (!hasCollection(collectionName)) {
logger.info("collection named " + collectionName + " not be found");
return null;
}
Query query = QueryEntityConverter.getQueryStataments(queryEntity);
List<? extends Object> listResult = getMongoTemplate().find(query, Object.class, collectionName);
return listResult;

}
 其中,QueryEntity是对分页排序参数的封装,通过它转换成查询对象Query,而entityClass的作用在于确定mongodb中的collection name,即集合名称,还有一个作用就是对query结果集的映射。在第二个方法中我们指定了collection name,用Object对象完成结果集映射。从而同样可以实现功能,而又拜托了实体绑定带来的繁琐。
2.插入 我们知道,mongodb是以一种类似于json的bson格式存放数据到文件中的,它的插入就是基于内存的文件读写操作。所以对数据的插入操作限制很宽泛,几乎任何形式的数据,只要能装换成json的数据或者json字符创的形式数据都可以直接插入,笔者也最喜欢这种简单有用的操作,是nosql基于文件的数据读写而不是结构化数据库技术执行SQL语句来实现数据的读写,在这点上,MongoDB可以对SQL注入攻击天然不感冒。下面是笔者实现的代码:
/**
* 批量插入
* @author young
* @param json
*            需要插入的json串文档
* @param collectionName
*            集合名称
*/
public void insertMany(Object json, String collectionName) {
Assert.notNull(json, "you can not save a null object into a collection");
Assert.notNull(collectionName, "collection name must be determined by parameter collectionName");
if (!hasCollection(collectionName)) {
logger.info("collection named " + collectionName + " not be found b");
}
getMongoTemplate().insert(json, collectionName);

}

/**
* 批量插入
* @author young
* @param batchToSave
*            插入文档列表
* @param collectionName
*            集合名称
*/
public <T> void insertMany(List<T> batchToSave, String collectionName) {
Assert.notNull(batchToSave, "you can not save a null object into a collection");
Assert.notNull(collectionName, "collection name must be determined by parameter collectionName");
if (!hasCollection(collectionName)) {
logger.info("collection named " + collectionName + " not be found ");
}
getMongoTemplate().insert(batchToSave, collectionName);
}

/**
* 批量插入
* @author young
* @param batchToSave
*            插入文档列表
* @param entityClass
*            确定集合名称的实体类
*/
public <T> void insertMany(List<T> batchToSave, Class<T> entityClass) {
Assert.notNull(batchToSave, "you can not save a null object into a collection");
Assert.notNull(entityClass, "entityClass must not be null");
if (!hasCollection(entityClass)) {
logger.info("collection  determied by entityClass not be found ");
return;
}
getMongoTemplate().insert(batchToSave, entityClass);
}
 3.更新 更新同插入、删除一样也是一种写操作。一般而言,对于数据库操作,性能瓶颈往往是是插入和查询,尤其是查询,尽管如此,更新数据也是必不可少的。MongoDB提供了丰富的更新操作,如更新某些字段的值的set操作符、给指定键的值增加值的inc操作符、修改某些collection的键值的rename操作符、删除集合中的某些键的unset操作符、给指定的键追加值的pull操作符、给某个数组键增加值的addToSet操作符、删除基于方向的第一个或者最后一个键的pop操作。如下是笔者实现的对指定键的值进行的批量更新操作:
/**
* @author young
* @param queryEntity
*            查询条件分装实体
* @param updateEntity
*            更新实体
* @param collectionName
*            集合名称
* @return
*/
public int updateMany(QueryEntity queryEntity, Map<String, Object> updateEntity, String collectionName) {
Assert.notNull(collectionName, "collection name must not be null ");
if (!hasCollection(collectionName)) {
logger.info("collection named " + collectionName + " not be found ");
return -1;
}
Query query = QueryEntityConverter.getQueryStataments(queryEntity);
Update update = UpdateEntityConverter.getKeysUpdateStatment(updateEntity);
return getMongoTemplate().updateMulti(query, update, collectionName).getN();
}

/**
* @author young
* @param queryEntity
*            查询条件分装实体
* @param updateEntity
*            更新实体
* @param entityClass
*            确定集合名称的实体类
* @return
*/
public <T> int updateMany(QueryEntity queryEntity, Map<String, Object> updateEntity, Class<T> entityClass) {
Assert.notNull(entityClass, "entityClass must not be null");
if (!hasCollection(entityClass)) {
logger.info("collection  determied by entityClass not be found ");
return -1;
}
Qu
1fff7
ery query = QueryEntityConverter.getQueryStataments(queryEntity);
Update update = UpdateEntityConverter.getKeysUpdateStatment(updateEntity);
return getMongoTemplate().updateMulti(query, update, entityClass).getN();
}
 4.删除 删除操作对于一个开发人员而言是必须谨慎的操作,以至于好些公司的所谓删除操作是假删除,防止操作失误带来的损失。MongoDB数据库的删除操作也是很谨慎的,如在MongoDB中插入数据,只需满足格式要求,可以反复插入除主键字段外其他部分都相同的数据;但是对于删除,MongoDB的态度就是“在某些模糊描述的情况下,宁可不删”。这本身就是MongoDB的一种特性。其实,任何数据库的删除操作,都可以分为查询和删除两部分,前者是为了标记需要删除的数据,后者才是对标记数据的清理操作,所以笔者以为,删除操作的性能很大程度上是依赖于查询,它们都依赖于数据组织方式,如索引的建立,当然了更新操作也一样。如下是笔者对删除的实现:
/**
* 删除指定集合中的指定文档
* @author young
* @param collectionName
*            集合名称
* @param queryEntity
*            查询条件封装实体类,确定了需要删除的文档
* @return
*/
public int removeMany(QueryEntity queryEntity, String collectionName) {
Assert.notNull(collectionName, "collection name must not be null ");
if (!hasCollection(collectionName)) {
logger.info("collection named " + collectionName + " not be found ");
return -1;
}
Query query = QueryEntityConverter.getQueryStataments(queryEntity);
return getMongoTemplate().remove(query, collectionName).getN();
}

/**
* 删除指定集合中的指定文档
* @author
* @param queryEntity
*            查询条件封装实体类,确定了需要删除的文档
* @param entityClass
*            确定集合名称的实体类
* @return
*/
public <T> int removeMany(QueryEntity queryEntity, Class<T> entityClass) {
Assert.notNull(entityClass, "entityClass must not be null");
if (!hasCollection(entityClass)) {
logger.info("collection  determied by entityClass not be found ");
return -1;
}
Query query = QueryEntityConverter.getQueryStataments(queryEntity);
return getMongoTemplate().remove(query, entityClass).getN();
}
 到现在为止,我们已经完成了对MongoDB中数据库的增删查改等基本操作,但是一款优秀的产品,高级特性才是闪光的地方。MongoDB而言,数据读写的高性能,在背后支持的是基于内存操作,以及丰富的索引支持,淡然了查询优化器的作用不可小觑,在这点上索引和数据归类划分是我们开发人员讨论的重点,既要保证数据划分有利于业务逻辑,又要保证读写性能;在本文的最后,需要指出的是MongoDB作为一种nosql数据库,也支持丰富的数据聚合和map-reduce操作,这也是笔者最有兴趣的地方,不过鉴于性能和数据库io负荷的考虑,后台执行应该是一种经过实践检验的不错方式。
 

 

 

 

 

 

 

 

 


阅读更多
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: