在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的安装等环境准备工作省略)。
/**
* 查询分页数据
* @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对象完成结果集映射。从而同样可以实现功能,而又拜托了实体绑定带来的繁琐。/**
* 批量插入
* @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.更新
/**
* @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;
}
Query query = QueryEntityConverter.getQueryStataments(queryEntity);
Update update = UpdateEntityConverter.getKeysUpdateStatment(updateEntity);
return getMongoTemplate().updateMulti(query, update, entityClass).getN();
}
4.删除
/**
* 删除指定集合中的指定文档
* @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负荷的考虑,后台执行应该是一种经过实践检验的不错方式。