在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,我们已经迫切需要使用它了,那我们必须实例化它,所以有必要认识一下它的构造子:
?可以看到,有四个构造函数可用,前俩个源码如下
?
class="java">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对象完成结果集映射。从而同样可以实现功能,而又拜托了实体绑定带来的繁琐。
/** * 批量插入 * @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提供了丰富的更新操作,如更新某些字段的值的setcaozuofu.html" target="_blank">操作符、给指定键的值增加值的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; } Query 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负荷的考虑,后台执行应该是一种经过实践检验的不错方式。
?
?
?
?
?
?
?
?