log4j.propertiesjava
log4j.rootLogger=INFO, stdout
log4j.logger.org.springframework.data.mongodb.core=DEBUG, mongodb log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target=System.out log4j.appender.stdout.Threshold=INFO log4j.appender.stdout.ImmediateFlush=true log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %X{RequestId} - %m%n log4j.appender.mongodb=org.apache.log4j.ConsoleAppender log4j.appender.mongodb.Target=System.out log4j.appender.mongodb.Threshold=DEBUG log4j.appender.mongodb.ImmediateFlush=true log4j.appender.mongodb.layout=org.apache.log4j.PatternLayout log4j.appender.mongodb.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %X{RequestId} - %m%n
缘由:spring
public class MongoTemplate implements MongoOperations, ApplicationContextAware {
private static final Logger LOGGER = LoggerFactory.getLogger(MongoTemplate.class); public void dropCollection(String collectionName) { execute(collectionName, new CollectionCallback<Void>() { public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException { collection.drop(); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Dropped collection [{}]", collection.getFullName()); } return null; } });
spring-data-mongodb中的实体映射是经过MongoMappingConverter这个类实现的。它能够经过注释把java类转换为mongodb的文档。mongodb
它有如下几种注释:
@Id - 文档的惟一标识,在mongodb中为ObjectId,它是惟一的,经过时间戳+机器标识+进程ID+自增计数器(确保同一秒内产生的Id不会冲突)构成。shell
@Document - 把一个java类声明为mongodb的文档,能够经过collection参数指定这个类对应的文档。数据库
@DBRef - 声明相似于关系数据库的关联关系。ps:暂不支持级联的保存功能,当你在本实例中修改了DERef对象里面的值时,单独保存本实例并不能保存DERef引用的对象,它要另外保存,以下面例子的Person和Account。express
@Indexed - 声明该字段须要索引,建索引能够大大的提升查询效率。apache
@CompoundIndex - 复合索引的声明,建复合索引能够有效地提升多字段的查询效率。api
@GeoSpatialIndexed - 声明该字段为地理信息的索引。数组
@Transient - 映射忽略的字段,该字段不会保存到mongodb。bash
@PersistenceConstructor - 声明构造函数,做用是把从数据库取出的数据实例化为对象。该构造函数传入的值为从DBObject中取出的数据。
如下引用一个官方文档的例子:
Person类
@Document(collection="person")
@CompoundIndexes({
@CompoundIndex(name = "age_idx", def = "{'lastName': 1, 'age': -1}", unique=true) }) public class Person<T extends Address> { @Id private String id; @Indexed(unique = true) private Integer ssn; private String firstName; @Indexed private String lastName; private Integer age; @Transient private Integer accountTotal; @DBRef private List<Account> accounts; private T address; public Person(Integer ssn) { this.ssn = ssn; } @PersistenceConstructor public Person(Integer ssn, String firstName, String lastName, Integer age, T address) { this.ssn = ssn; this.firstName = firstName; this.lastName = lastName; this.age = age; this.address = address; }
Account类
1 @Document
2 public class Account { 3 4 @Id 5 private ObjectId id; 6 private Float total; 7 8 }
单列索引:@Indexed
调用mongoTemplate的save方法时, spring-data-mongodb的TypeConverter会自动给document添加一个_class属性, 值是你保存的类名. 这种设计并无什么坏处. spring-data-mongodb是为了在把document转换成Java对象时可以转换到具体的子类. 但有时候咱们并不但愿出现这个字段, 主要是看上去会比较"烦". 能够经过设置MappingMongoConverter的MongoTypeMapper来解决这个问题.
DefaultMongoTypeMapper类的构造函数的第一个参数是Type在MongoDB中名字. 设置为null的话就不会在保存时自动添加_class属性.因此须要覆写
spring的配置文件方式:
<mongo:mongo host="localhost" port="27017" /> <mongo:db-factory dbname="database" /> <bean id="mappingContext" class="org.springframework.data.mongodb.core.mapping.MongoMappingContext" /> <bean id="defaultMongoTypeMapper" class="org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper"> <constructor-arg name="typeKey"><null/></constructor-arg> </bean> <bean id="mappingMongoConverter" class="org.springframework.data.mongodb.core.convert.MappingMongoConverter"> <constructor-arg name="mongoDbFactory" ref="mongoDbFactory" /> <constructor-arg name="mappingContext" ref="mappingContext" /> <property name="typeMapper" ref="defaultMongoTypeMapper" /> </bean> <bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate"> <constructor-arg name="mongoDbFactory" ref="mongoDbFactory" /> <constructor-arg name="mongoConverter" ref="mappingMongoConverter" /> </bean>
JavaConfig方式:
@EnableMongoRepositories( basePackages = {"com.dhp"}, repositoryFactoryBeanClass = DHBMongoRepositoryFactoryBean.class ) @PropertySource("classpath:mongo.properties") @EnableMongoAuditing public class MongoConfig extends AbstractMongoConfiguration { @Value("${mongo.connectionString}") private String connectionString; @Value("${mongo.dbName}") private String dbName; @Autowired private ApplicationContext appContext; @Override protected String getDatabaseName() { return dbName; } @Override @Bean public Mongo mongo() throws Exception { MongoClientURI mongoClientURI = new MongoClientURI(connectionString); return new MongoClient(mongoClientURI); } @Override @Bean public MongoTemplate mongoTemplate() throws Exception { MongoDbFactory factory = mongoDbFactory(); MongoMappingContext mongoMappingContext = new MongoMappingContext(); mongoMappingContext.setApplicationContext(appContext); MappingMongoConverter converter = new MappingMongoConverter(new DefaultDbRefResolver(factory), mongoMappingContext); converter.setTypeMapper(new DefaultMongoTypeMapper(null)); return new MongoTemplate(factory, converter); } @Bean public static PropertySourcesPlaceholderConfigurer propertyConfigInDev() { return new PropertySourcesPlaceholderConfigurer(); } }
当使用Servlet 3初始化确保添加应用程序上下文到mongo中,若是不加上这两句,会报异常:org.springframework.expression.spel.SpelEvaluationException
@Autowired
private ApplicationContext appContext;
mongoMappingContext.setApplicationContext(appContext);
public class TagProperty {
private String type; private int count; }
@Document(collection = "tag")
public class Tag extends BaseEntity { @Field("user_id") @Indexed private String userId; //key->标签文本 value->标签属性 private Map<String, TagProperty> tags; }
效果:
/* 1 */
{
"_id" : ObjectId("581074c63145d5e8cc498db7"), "_class" : "nd.sdp.idea.modules.tag.entity.Tag", "user_id" : "214372", "tags" : { "设计技巧" : { "type" : "default", "count" : 1 }, "生活启发" : { "type" : "default", "count" : 23 }, "随笔" : { "type" : "user", "count" : 0 } }, "delete_flag" : false }
这种形式的嵌套适用于一对多的状况,里面是key-value的形式,也便于删除和修改。再如:
@Document(collection = "locations") public class Location extends BaseEntity { @Field(value = "user_id") private String userId; private Set<String> locations; }
一对一的时候,也能够这样设计:
@Document(collection = "idea_logs")
@CompoundIndexes(
@CompoundIndex(name = "_ii_df_idx_", def = "{'ideaId':1, 'deleteFlag':1}") ) public class IdeaLog extends BaseEntity { @Field(value = "idea_id") private String ideaId; private String info; @Field(value = "create_at") private Long createAt; private Operator operator; @Field(value = "erp_order") private ErpOrder erpOrder; private String evaluation; }
public class Operator { @Field(value = "user_id") private String userId; @Field(value = "user_name") private String userName; }
但嵌套自己存在须要注意的问题,好比嵌套内容数据量的大小,对内嵌文档的删除、修改是否便利等等。
下面这种设计就不便于操做:
{ username: <用户名>, password: <密码>, tasks: [ { taskname: <任务名>, taskinfo: <任务描述> },{ taskname: <任务名>, taskinfo: <任务描述> }...... ] }
这是能够修改成user和task2个文档,task中包含user的id。
1 索引
1.1 单列索引
@Indexed
@Field(value = "delete_flag") private Boolean deleteFlag = false;
@Indexed属性:name定义索引名称、unique是否为惟一索引,默认false
1.2 组合索引
@Document(collection = "#{T(com.nd.social.common.handler.TenantHandler).getTablePrefix().concat('block_content')}")
@CompoundIndexes(
@CompoundIndex(name = "idx_bc_t_sc_s", def = "{'tenant':1,'scope.code':1,'sourceId':1}", unique = true) ) @TypeAlias("BlockContent") @CompoundIndexes( @CompoundIndex(name = "_ui_s_df_idx_", def = "{'userId':1, 'status':1, 'deleteFlag':1}", unique = true) )
2 注意
在自定义接口实现中使用数据库中的字段名做为查询条件,而不是实体类的属性名
若是进行+1操做 尽可能使用inc 避免并发问题
3 排序
private static final Sort SORT_BY_CREATE_TIME_DESC =
new Sort(Sort.Direction.DESC, "createAt");
List<Idea> finByUserIdAndDeleteFlagFalse(String userId, Sort sort);
命名查询:OrderBy...Desc/ASC
List<Idea> findByUserIdAndStatusInAndViewAtLessThanAndDeleteFlagFalseOrderByCreateAtDesc(String userId, List<IdeaStatus> status, long viewAt);
4 分页
1 offset/limit
private Pageable getPageable(SearchVo condition) {
int limit = condition.getLimit(); int offset = condition.getOffset(); return new PageRequest(offset / limit, limit, SORT_BY_CREATE_TIME_DESC); }
private int offset = 0;
private int limit = 15; public int getOffset() { return offset; } public void setOffset(int offset) { this.offset = offset; } public int getLimit() { return limit <= 0 ? 15 : limit; } public void setLimit(int limit) { this.limit = limit; }
2 page/size
page 页码,请求第几页数据(默认值:1) 可选
size 每页数量(默认值:30) 可选 new PageRequest(page - 1, size)
3 计算总页数
pageVo.setTotalPage(size == 0 ? 1 : (int) Math.ceil((double) total / (double) size));
4 结果集示例
public class PageVo<T> {
// 总数
private long totalCount; // 总页数 private int totalPage; // 页码 private int page; // 单页数量 private int size; // 结果列表 private List<T> items; public List<T> getItems() { return items; } public void setItems(List<T> items) { this.items = items; } public long getTotalCount() { return totalCount; } public void setTotalCount(long totalCount) { this.totalCount = totalCount; } public int getTotalPage() { return totalPage; } public void setTotalPage(int totalPage) { this.totalPage = totalPage; } public int getPage() { return page; } public void setPage(int page) { this.page = page; } public int getSize() { return size; } public void setSize(int size) { this.size = size; } }
public class Items<T> {
// 结果列表 能够是Set/List
private Collection<T> items; // 若是不须要 能够设置为items的size值 private long totalCount; public static <T> Items<T> of(Collection<T> list) { Items<T> items = new Items<>(); items.setItems(list); items.setTotalCount(list.size()); return items; } public static <T> Items<T> of(Collection<T> list, long totalCount) { Items<T> items = new Items<>(); items.setItems(list); items.setTotalCount(totalCount); return items; } public Collection<T> getItems() { return items; } public void setItems(Collection<T> items) { this.items = items; } public long getTotalCount() { return totalCount; } public void setTotalCount(long totalCount) { this.totalCount = totalCount; } }
5 打印mongo NoSql语句
显示操做mongo的语句,log4j.properties里面加入:
1 log4j.logger.org.springframework.data.mongodb.core=DEBUG, mongodb
2
3 log4j.appender.mongodb=org.apache.log4j.ConsoleAppender 4 log4j.appender.mongodb.Target=System.out 5 log4j.appender.mongodb.Threshold=DEBUG 6 log4j.appender.mongodb.ImmediateFlush=true 7 log4j.appender.mongodb.layout=org.apache.log4j.PatternLayout 8 log4j.appender.mongodb.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %X{RequestId} - %m%n
缘由:
在mongo的底层实现中,如MongoTemplate中,判断了是否日志级别为debug,是的时候会打印语句出来,例如
1 private static final Logger LOGGER = LoggerFactory.getLogger(MongoTemplate.class);
2
3 protected void executeQuery(Query query, String collectionName, DocumentCallbackHandler dch, 4 CursorPreparer preparer) { 5 6 Assert.notNull(query); 7 8 DBObject queryObject = queryMapper.getMappedObject(query.getQueryObject(), null); 9 DBObject sortObject = query.getSortObject(); 10 DBObject fieldsObject = query.getFieldsObject(); 11 12 if (LOGGER.isDebugEnabled()) { 13 LOGGER.debug("Executing query: {} sort: {} fields: {} in collection: {}", serializeToJsonSafely(queryObject), 14 sortObject, fieldsObject, collectionName); 15 } 16 17 this.executeQueryInternal(new FindCallback(queryObject, fieldsObject), preparer, dch, collectionName); 18 }
6 注解查询
1 一个方法命名查询中同一个属性不能出现2次 可使用@Query注解查询
2 @Query:
value 查询语句
count 做为统计的查询 返回int值
delete 做为删除语句并返回删除后的文档集合
fields 限定须要返回哪些字段
示例:
@Query(count = true, value = "{'$and':[{'tenant':?3},{'reportStatus':?0}," +
" {'dealTime':{'$gte':?1}}, {'dealTime':{'$lte':?2}}]}")
int countByStatusAndDealTimeBetween(ReportStatus status, Date begin, Date end, long tenant); @Query("{'$and':[{'userId':?0},{'deleteFlag':false}," + "{'$or':[{'content':{'$regex':?1}},{'location':{'$regex':?1}},{'createAtStr':{'$regex':?1}}]}]}") List<Idea> findByKeyWord(String userId, String key, Pageable pageable);
1 {
2 '$and': [ 3 { 4 'userId': ?0 5 }, 6 { 7 'deleteFlag': false 8 }, 9 { 10 '$or': [ 11 { 12 'content': { 13 '$regex': ?1 14 } 15 }, 16 { 17 'location': { 18 '$regex': ?1 19 } 20 }, 21 { 22 'createAtStr': { 23 '$regex': ?1 24 } 25 } 26 ] 27 } 28 ] 29 }
7 MongoOptions/MongoTemplate
public <T> T findOne(Query query, Class<T> entityClass)
public boolean exists(Query query, Class<?> entityClass) public <T> List<T> find(Query query, Class<T> entityClass) public <T> T findById(Object id, Class<T> entityClass) public <T> T findAndModify(Query query, Update update, Class<T> entityClass) public <T> T findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass) public class FindAndModifyOptions { boolean returnNew; // 是否返回更新后的值 boolean upsert; // 没有找到是否插入 boolean remove; // 找到是否删除 } public <T> T findAndRemove(Query query, Class<T> entityClass) public long count(Query query, Class<?> entityClass) public void insert(Object objectToSave) public void insert(Collection<? extends Object> batchToSave, Class<?> entityClass) public void insertAll(Collection<? extends Object> objectsToSave) public void save(Object objectToSave) 保存/修改 public WriteResult upsert(Query query, Update update, Class<?> entityClass) public WriteResult updateFirst(Query query, Update update, Class<?> entityClass) public WriteResult updateMulti(Query query, Update update, Class<?> entityClass) public WriteResult remove(Object object) public WriteResult remove(Query query, String collectionName) public <T> List<T> findAll(Class<T> entityClass) public <T> List<T> findAllAndRemove(Query query, Class<T> entityClass)
public DB getDb()
DBCollection getCollection(String collectionName); DBCollection 中包含各类CRUD操做以及对集合自己的定义操做(索引、命名)
public String getCollectionName(Class<?> entityClass)
public <T> MapReduceResults<T> mapReduce(String inputCollectionName, String mapFunction, String reduceFunction, Class<T> entityClass)
public <T> MapReduceResults<T> mapReduce(String inputCollectionName, String mapFunction, String reduceFunction,MapReduceOptions mapReduceOptions, Class<T> entityClass) public <T> GroupByResults<T> group(String inputCollectionName, GroupBy groupBy, Class<T> entityClass) public <O> AggregationResults<O> aggregate(TypedAggregation<?> aggregation, Class<O> outputType) public <O> AggregationResults<O> aggregate(Aggregation aggregation, Class<?> inputType, Class<O> outputType)
distinct方法:
public List<String> distinctUserId() {
return mongoTemplate.getCollection("ideas").distinct("user_id"); } public List<String> distinctLocation(String userId) { DBObject query = Query.query(Criteria.where("user_id").is(userId)).getQueryObject(); return mongoTemplate.getCollection("ideas").distinct("location", query); }
Sort
private final List<Order> orders;
public Sort and(Sort sort) { if (sort == null) { return this; } ArrayList<Order> these = new ArrayList<Order>(this.orders); for (Order order : sort) { these.add(order); } return new Sort(these); }
Query
1 private Sort sort;
2 private int skip; 3 private int limit; 4 5 public Query skip(int skip) { 6 this.skip = skip; 7 return this; 8 } 9 10 public Query limit(int limit) { 11 this.limit = limit; 12 return this; 13 } 14 15 public Query with(Pageable pageable) { 16 17 if (pageable == null) { 18 return this; 19 } 20 21 this.limit = pageable.getPageSize(); 22 this.skip = pageable.getOffset(); 23 24 return with(pageable.getSort()); 25 } 26 27 public Query with(Sort sort) { 28 29 if (sort == null) { 30 return this; 31 } 32 33 for (Order order : sort) { 34 if (order.isIgnoreCase()) { 35 throw new IllegalArgumentException(String.format("Gven sort contained an Order for %s with ignore case! " 36 + "MongoDB does not support sorting ignoreing case currently!", order.getProperty())); 37 } 38 } 39 40 if (this.sort == null) { 41 this.sort = sort; 42 } else { 43 this.sort = this.sort.and(sort); 44 } 45 46 return this; 47 } 48 49 50 51 private final Map<String, CriteriaDefinition> criteria = new LinkedHashMap<String, CriteriaDefinition>(); 52 53 public static Query query(CriteriaDefinition criteriaDefinition) { 54 return new Query(criteriaDefinition); 55 } 56 57 public Query() {} 58 59 60 public Query(CriteriaDefinition criteriaDefinition) { 61 addCriteria(criteriaDefinition); 62 } 63 64 65 public Query addCriteria(CriteriaDefinition criteriaDefinition) { 66 67 CriteriaDefinition existing = this.criteria.get(criteriaDefinition.getKey()); 68 String key = criteriaDefinition.getKey(); 69 70 if (existing == null) { 71 this.criteria.put(key, criteriaDefinition); 72 } else { 73 throw new InvalidMongoDbApiUsageException("Due to limitations of the com.mongodb.BasicDBObject, " 74 + "you can't add a second '" + key + "' criteria. " + "Query already contains '" 75 + existing.getCriteriaObject() + "'."); 76 } 77 78 return this; 79 }
Criteria
private String key;
private List<Criteria> criteriaChain; private LinkedHashMap<String, Object> criteria = new LinkedHashMap<String, Object>(); private Object isValue = NOT_SET; public static Criteria where(String key) { return new Criteria(key); }
public Criteria and(String key) { return new Criteria(this.criteriaChain, key); } is ne lt lte gt gte in/all nin mod size exits type not regex in: 包含其中一个便可 all:所有包含才能够 查询时要明确这多个值主键的关系是什么样的
public Criteria orOperator(Criteria... criteria)
public Criteria andOperator(Criteria... criteria)
public Criteria norOperator(Criteria... criteria)
8 案例
1 按照建立时间查找上一条下一条记录
public Idea findIdeaNearTo(String userId, long createAt, boolean isNext) {
Criteria criteria = Criteria.where("user_id").is(userId).and("delete_flag").is(false); Query query; if (isNext) { query = new Query(criteria).with(new Sort(Sort.Direction.ASC, "create_at")); criteria.and("create_at").gt(createAt); } else { query = new Query(criteria).with(new Sort(Sort.Direction.DESC, "create_at")); criteria.and("create_at").lt(createAt); } return mongoTemplate.findOne(query, Idea.class); }
next: { "user_id" : "2107164232" , "delete_flag" : false , "create_at" : { "$gt" : 1474600921000}} pre: { "user_id" : "2107164232" , "delete_flag" : false , "create_at" : { "$lt" : 1474600921000}}
2 orOperator / andOperator
public List<Idea> find(String userId, IdeaStatus status, OriginalityType type, long createAtFrom, long createAtTo) {
Criteria criteria = Criteria.where("user_id").is(userId) .and("delete_flag").is(false) .and("status").in(status); if (type == null) { criteria.orOperator( Criteria.where("originality_type").exists(false), // 字段是否存在 exists Criteria.where("originality_type").size(0)); // 字段是数组,大小 size } else { criteria.and("originality_type").in(type); } criteria.andOperator( Criteria.where("create_at").gte(createAtFrom), Criteria.where("create_at").lt(createAtTo) ); return mongoTemplate.find(new Query(criteria), Idea.class); }
1 {
2 "user_id": "290536", 3 "delete_flag": false, 4 "status": { 5 "$in": [ 6 "originality" 7 ] 8 }, 9 "$or": [ 10 { 11 "originality_type": { 12 "$exists": false 13 } 14 }, 15 { 16 "originality_type": { 17 "$size": 0 18 } 19 } 20 ], 21 "$and": [ 22 { 23 "create_at": { 24 "$gte": 1445788800000 25 } 26 }, 27 { 28 "create_at": { 29 "$lt": 1446393600000 30 } 31 } 32 ] 33 }
注意:一个字段有多个条件限制,须要使用多个Criteria实现。各个Criteria之间使用orOperator或andOperator连接。
案例2
1 public Items<Idea> listOriginalities(String userId, SearchVo condition, Pageable pageable) {
2 List<Criteria> orCriteriaList = new ArrayList<>(); 3 if (condition.getStatus() == null || condition.getStatus().size() == 0) { 4 orCriteriaList.add(Criteria.where("status").in(Arrays.asList(IdeaStatus.originality))); 5 } else { 6 for (IdeaStatus status : condition.getStatus()) { 7 orCriteriaList.add(Criteria.where("status").all(Arrays.asList(status, IdeaStatus.originality))); 8 } 9 } 10 Criteria criteria = Criteria.where("userId").is(userId).and("deleteFlag").is(false) 11 .orOperator(orCriteriaList.toArray(new Criteria[0])); 12 13 if (!CollectionUtils.isEmpty(condition.getTag())) { 14 criteria.and("tags").in(condition.getTag()); 15 } 16 Query query = query(criteria).with(pageable); 17 Query countQuery = query(criteria); 18 return Items.of(mongoTemplate.find(query, Idea.class), mongoTemplate.count(countQuery, Idea.class)); 19 }
{ "user_id": "2107164232", "delete_flag": false, "$or": [ { "status": { "$all": [ "discussing", "originality" ] } }, { "status": { "$all": [ "considering", "originality" ] } } ] }
localhost:9088/v0.1/ideas/originality?tag=系统默认&status=discussing,considering
要求status必须是originality,若条件中包含status,必须是其中之一或全是。
public Criteria orOperator(Criteria... criteria)
把条件中每个状态拆分与originality组成一个查询条件,这样就获得一组查询条件,每一个条件是or的关系。
3 更新或保存 更新返回旧值
public BlockContent addIfNotExists(BlockContent blockContent, long tenant) {
Query query = Query.query(where("tenant").is(tenant) .and("sourceId").is(blockContent.getSourceId()) .and("scope.code").is(blockContent.getScope().getCode())); Update update = getUpdate(blockContent); return operations.findAndModify( query, update, options().upsert(true).returnNew(false), BlockContent.class ); } 返回null 保存 返回旧值 更新 使用他的id
3 更新语句避免 n+1操做
public void setBlockStatusAndRecoverTime(Set<String> ids, long tenant, Date recoverTime) {
Query query = Query.query( where("_id").in(ids) .and("tenant").is(tenant) .and("blockStatus").is(BlockStatus.BLOCKED)); operations.updateMulti( query, Update.update("blockStatus", BlockStatus.RECOVER) .set("recoverTime", recoverTime), BlockContent.class ); }
对于同样的修改动做,尤为更新少许字段时候(deleteFlag,dealTime,status),使用一条更新语句更新多个字段(updateMulti),而不是先查询,修改后保存
public Object testAggregation1() {
TypedAggregation<News> aggregation = Aggregation.newAggregation( News.class, project("evaluate"), group("evaluate").count().as("totalNum"), match(Criteria.where("totalNum").gte(85)), sort(Sort.Direction.DESC, "totalNum") ); AggregationResults<BasicDBObject> result = template.aggregate(aggregation, BasicDBObject.class); // 语句执行以下: // { // "aggregate": "news", // "pipeline": [ // { // "$project": { // "evaluate": "$eval" // } // }, // { // "$group": { // "_id": "$evaluate", // "totalNum": { // "$sum": 1 // } // } // }, // { // "$match": { // "totalNum": { // "$gte": 85 // } // } // }, // { // "$sort": { // "totalNum": -1 // } // } // ] // } // 查询结果:[{ "_id" : 0 , "totalNum" : 5033}, { "_id" : 1 , "totalNum" : 4967}] --> {"0": 5033,"1": 4967} List<BasicDBObject> resultList = result.getMappedResults(); Map<Integer, Object> map = Maps.newHashMap(); for (BasicDBObject dbo : resultList) { int eval = dbo.getInt("_id"); long num = dbo.getLong("totalNum"); map.put(eval, num); } return map; //使用此方法,若是封装好了某一个类,类里面的属性和结果集的属性一一对应,那么,Spring是能够直接把结果集给封装进去的 //就是AggregationResults<BasicDBObject> result = mongoTemplate.aggregate(agg, BasicDBObject); // 中的BasicDBObject改成本身封装的类 //可是感受这样作有点不灵活,其实吧,应该是本身如今火候还不到,还看不到他的灵活性,好处在哪里;等火候旺了再说呗 //因此,就用这个万能的BasicDBObject类来封装返回结果 }
public Object testAggregation2() {
TypedAggregation<News> aggregation = Aggregation.newAggregation( News.class, project("evaluate"), group("evaluate").count().as("totalNum"), match(Criteria.where("totalNum").gte(85)), sort(Sort.Direction.DESC, "totalNum"), project("evaluate", "totalNum").and("eval").previousOperation() //为分组的字段(_id)创建别名 ); AggregationResults<BasicDBObject> result = template.aggregate(aggregation, BasicDBObject.class); // 语句执行以下: // { // "aggregate": "news", // "pipeline": [ // { // "$project": { // "evaluate": "$eval" // } // }, // { // "$group": { // "_id": "$evaluate", // "totalNum": { // "$sum": 1 // } // } // }, // { // "$match": { // "totalNum": { // "$gte": 85 // } // } // }, // { // "$sort": { // "totalNum": -1 // } // } // ] // } // 查询结果:[{ "eval" : 0 , "totalNum" : 5033}, { "eval" : 1 , "totalNum" : 4967}] --> {"0": 5033,"1": 4967} List<BasicDBObject> resultList = result.getMappedResults(); Map<Integer, Object> map = Maps.newHashMap(); for (BasicDBObject dbo : resultList) { int eval = dbo.getInt("eval"); long num = dbo.getLong("totalNum"); map.put(eval, num); } return map; }
/**
* 功能:unwind()的使用,经过Spring Data MongoDB
* unwind()就是$unwind这个命令的转换,
* $unwind - 能够将一个包含数组的文档切分红多个, 好比你的文档有 中有个数组字段 A, A中有10个元素, 那么
* 通过 $unwind处理后会产生10个文档,这些文档只有 字段 A不一样
* 详见:http://my.oschina.net/GivingOnenessDestiny/blog/88006
*/
public Object testAggregation3() {
TypedAggregation<News> agg = Aggregation.newAggregation( News.class, unwind("classKey"), project("evaluate", "classKey"), // 这里说明一点就是若是group>=2个字段,那么结果集的分组字段就没有_id了,取而代之的是具体的字段名(和testAggregation()对比) group("evaluate", "classKey").count().as("totalNum"), sort(Sort.Direction.DESC, "totalNum") ); AggregationResults<NewsVo> result = template.aggregate(agg, NewsVo.class); return result.getMappedResults(); /* { "aggregate": "news", "pipeline": [ { "$unwind": "$ckey" }, { "$project": { "evaluate": "$eval", "classKey": "$ckey" } }, { "$group": { "_id": { "evaluate": "$evaluate", "classKey": "$classKey" }, "totalNum": { "$sum": 1 } } }, { "$sort": { "totalNum": -1 } } ] }*/ // [ // { // "evaluate": "0", // "class_key": "26", // "total_num": 2457 // }, // { // "evaluate": "0", // "class_key": "A102", // "total_num": 2449 // }, // { // "evaluate": "0", // "class_key": "24", // "total_num": 2446 // } // ] }
/**
* db.videos.aggregate(
[
{ $match: { "frags.isnew" : true } },
{ $unwind: "$frags" },
{ $match: { "frags.isnew" : true } },
{ $group: {
_id: {cat1:"$cat1"},
count: { $sum: 1 },
publishdate2: { $max: "$publishdate"}
}
}
]
)
*/
Aggregation agg = newAggregation( project("frags","cat1","publishdate"),//挑选所需的字段 match( Criteria.where("frags.isnew").is(Boolean.TRUE) .and("cat1").in(importantCat1List) ),//筛选符合条件的记录 unwind("frags"),//若是有MASTER-ITEM关系的表,需同时JOIN这两张表的,展开子项LIST,且是内连接,即若是父和子的关联ID没有的就不会输出 match(Criteria.where("frags.isnew").is(Boolean.TRUE)), group("cat1")//设置分组字段 .count().as("updateCount")//增长COUNT为分组后输出的字段 .last("publishdate").as("publishDate"),//增长publishDate为分组后输出的字段 project("publishDate","cat1","updateCount")//从新挑选字段 .and("cat1").previousOperation()//为前一操做所产生的ID FIELD创建别名 ); Aggregation agg = newAggregation( project("authorName"), group("authorName").count().as("sum"), sort(sort), limit(5) ); db.getCollection('ideas').aggregate([ {$match:{"delete_flag":false}}, {$group:{ _id:"$user_id", count:{$sum:NumberLong(1)} }}, {$match:{"count":{$gt:10}}} ]); {"_id" : "551314", "count" : NumberLong(6)} {"_id" : "201960", "count" : NumberLong(10)} project(String... fields) unwind(String field) group(String... fields) and count sum avg min max last first addToSet as 取名字 sort(Sort sort) skip(int elementsToSkip) limit(long maxElements) match(Criteria criteria) public int countInfractionUserAmount(Date begin, Date end, long tenant) { Aggregation aggregation = newAggregation( match(where("tenant").is(tenant) .andOperator(where("time").gte(begin), where("time").lte(end))), project("uids"), unwind("uids"), group("uids"), group.count().as("count") ); AggregationResults<Map> results = mongoOperations.aggregate( aggregation, getInfractionCollection(), Map.class); return MongoUtils.parseAggregationCount(results); } public static int parseAggregationCount(AggregationResults<Map> results) { List<Map> maps = results.getMappedResults(); if (CollectionUtils.isEmpty(maps)) { return 0; } return Integer.parseInt(maps.get(0).get("count").toString()); }
管道操做符详细使用说明
1. $project: 数据投影,主要用于重命名、增长和删除字段
例如:
db.article.aggregate(
{ $project : {
title : 1 ,
author : 1 ,
}}
);
这样的话结果中就只还有_id,tilte和author三个字段了,默认状况下_id字段是被包含的,若是要想不包含_id话能够这样:
db.article.aggregate(
{ $project : {
_id : 0 ,
title : 1 ,
author : 1
}});
也能够在$project内使用算术类型表达式操做符,例如:
db.article.aggregate(
{ $project : {
title : 1,
doctoredPageViews : { $add:["$pageViews", 10] }
}});
经过使用$add给pageViews字段的值加10,而后将结果赋值给一个新的字段:doctoredPageViews
注:必须将$add计算表达式放到中括号里面
除此以外使用$project还能够重命名字段名和子文档的字段名:
db.article.aggregate(
{ $project : {
title : 1 ,
page_views : "$pageViews" ,
bar : "$other.foo"
}});
也能够添加子文档:
db.article.aggregate(
{ $project : {
title : 1 ,
stats : {
pv : "$pageViews",
foo : "$other.foo",
dpv : { $add:["$pageViews", 10] }
}
}});
产生了一个子文档stats,里面包含pv,foo,dpv三个字段。
2.$match: 滤波操做,筛选符合条件文档,做为下一阶段的输入
$match的语法和查询表达式(db.collection.find())的语法相同
db.articles.aggregate( [
{ $match : { score : { $gt : 70, $lte : 90 } } },
{ $group: { _id: null, count: { $sum: 1 } } }
] );
$match用于获取分数大于70小于或等于90记录,而后将符合条件的记录送到下一阶段$group管道操做符进行处理。
注意:1.不能在$match操做符中使用$where表达式操做符。
2.$match尽可能出如今管道的前面,这样能够提前过滤文档,加快聚合速度。
3.若是$match出如今最前面的话,可使用索引来加快查询。
3. $limit: 限制通过管道的文档数量
$limit的参数只能是一个正整数
db.article.aggregate(
{ $limit : 5 });
这样的话通过$limit管道操做符处理后,管道内就只剩下前5个文档了
4. $skip: 从待操做集合开始的位置跳过文档的数目
$skip参数也只能为一个正整数
db.article.aggregate(
{ $skip : 5 });
通过$skip管道操做符处理后,前五个文档被“过滤”掉
5.$unwind:将数组元素拆分为独立字段
例如:article文档中有一个名字为tags数组字段:
> db.article.find()
{ "_id" : ObjectId("528751b0e7f3eea3d1412ce2"),
"author" : "Jone", "title" : "Abook",
"tags" : [ "good", "fun", "good" ] }
使用$unwind操做符后:
> db.article.aggregate({$project:{author:1,title:1,tags:1}},{$unwind:"$tags"})
{
"result" : [
{
"_id" : ObjectId("528751b0e7f3eea3d1412ce2"),
"author" : "Jone",
"title" : "A book",
"tags" : "good"
},
{
"_id" : ObjectId("528751b0e7f3eea3d1412ce2"),
"author" : "Jone",
"title" : "A book",
"tags" : "fun"
},
{
"_id" : ObjectId("528751b0e7f3eea3d1412ce2"),
"author" : "Jone",
"title" : "A book",
"tags" : "good"
}
],
"ok" : 1
}
注意:a.{$unwind:"$tags"})不要忘了$符号
b.若是$unwind目标字段不存在的话,那么该文档将被忽略过滤掉,例如:
> db.article.aggregate({$project:{author:1,title:1,tags:1}},{$unwind:"$tag"})
{ "result" : [ ], "ok" : 1 }
将$tags改成$tag因不存在该字段,该文档被忽略,输出的结果为空
c.若是$unwind目标字段不是一个数组的话,将会产生错误,例如:
> db.article.aggregate({$project:{author:1,title:1,tags:1}},{$unwind:"$title"})
Error: Printing Stack Trace
at printStackTrace (src/mongo/shell/utils.js:37:15)
at DBCollection.aggregate (src/mongo/shell/collection.js:897:9)
at (shell):1:12
Sat Nov 16 19:16:54.488 JavaScript execution failed: aggregate failed: {
"errmsg" : "exception: $unwind: value at end of field path must be an array",
"code" : 15978,
"ok" : 0
} at src/mongo/shell/collection.js:L898
d.若是$unwind目标字段数组为空的话,该文档也将会被忽略。
6.$group 对数据进行分组
$group的时候必需要指定一个_id域,同时也能够包含一些算术类型的表达式操做符:
db.article.aggregate(
{ $group : {
_id : "$author",
docsPerAuthor : { $sum : 1 },
viewsPerAuthor : { $sum : "$pageViews" }
}});
注意: 1.$group的输出是无序的。
2.$group操做目前是在内存中进行的,因此不能用它来对大量个数的文档进行分组。
7.$sort : 对文档按照指定字段排序
使用方式以下:
db.users.aggregate( { $sort : { age : -1, posts: 1 } });
按照年龄进行降序操做,按照posts进行升序操做
注意:1.若是将$sort放到管道前面的话能够利用索引,提升效率
2.MongoDB 24.对内存作了优化,在管道中若是$sort出如今$limit以前的话,$sort只会对前$limit个文档进行操做,这样在内存中也只会保留前$limit个文档,从而能够极大的节省内存
3.$sort操做是在内存中进行的,若是其占有的内存超过物理内存的10%,程序会产生错误
管道操做符做为“键”,所对应的“值”叫作管道表达式。例如上面例子中{$match:{status:"A"}},$match称为管道操做符,而{status:"A"}称为管道表达式,它能够看做是管道操做符的操做数(Operand),每一个管道表达式是一个文档结构,它是由字段名、字段值、和一些表达式操做符组成的,例如上面例子中管道表达式就包含了一个表达式操做符$sum进行累加求和。
每一个管道表达式只能做用于处理当前正在处理的文档,而不能进行跨文档的操做。管道表达式对文档的处理都是在内存中进行的。除了可以进行累加计算的管道表达式外,其余的表达式都是无状态的,也就是不会保留上下文的信息。累加性质的表达式操做符一般和$group操做符一块儿使用,来统计该组内最大值、最小值等,例如上面的例子中咱们在$group管道操做符中使用了具备累加的$sum来计算总和。
除了$sum觉得,还有如下性质的表达式操做符:
组聚合操做符
Name |
Description |
Returns an array of all the unique values for the selected field among for each document in that group. |
|
Returns the first value in a group. |
|
Returns the last value in a group. |
|
Returns the highest value in a group. |
|
Returns the lowest value in a group. |
|
Returns an average of all the values in a group. |
|
Returns an array of all values for the selected field among for each document in that group. |
|
Returns the sum of all the values in a group. |
Bool类型聚合操做符
Name |
Description |
Returns true only when all values in its input array are true. |
|
Returns true when any value in its input array are true. |
|
Returns the boolean value that is the opposite of the input value. |
比较类型聚合操做符
Name |
Description |
Compares two values and returns the result of the comparison as an integer. |
|
Takes two values and returns true if the values are equivalent. |
|
Takes two values and returns true if the first is larger than the second. |
|
Takes two values and returns true if the first is larger than or equal to the second. |
|
Takes two values and returns true if the second value is larger than the first. |
|
Takes two values and returns true if the second value is larger than or equal to the first. |
|
Takes two values and returns true if the values are not equivalent. |
算术类型聚合操做符
Name |
Description |
Computes the sum of an array of numbers. |
|
Takes two numbers and divides the first number by the second. |
|
Takes two numbers and calcualtes the modulo of the first number divided by the second. |
|
Computes the product of an array of numbers. |
|
Takes two numbers and subtracts the second number from the first. |
字符串类型聚合操做符
Name |
Description |
Concatenates two strings. |
|
Compares two strings and returns an integer that reflects the comparison. |
|
Takes a string and returns portion of that string. |
|
Converts a string to lowercase. |
|
Converts a string to uppercase. |
日期类型聚合操做符
Name |
Description |
Converts a date to a number between 1 and 366. |
|
Converts a date to a number between 1 and 31. |
|
Converts a date to a number between 1 and 7. |
|
Converts a date to the full year. |
|
Converts a date into a number between 1 and 12. |
|
Converts a date into a number between 0 and 53 |
|
Converts a date into a number between 0 and 23. |
|
Converts a date into a number between 0 and 59. |
|
Converts a date into a number between 0 and 59. May be 60 to account for leap seconds. |
|
Returns the millisecond portion of a date as an integer between 0 and 999. |
条件类型聚合操做符
Name |
Description |
A ternary operator that evaluates one expression, and depending on the result returns the value of one following expressions. |
|
Evaluates an expression and returns a value. |
注:以上操做符都必须在管道操做符的表达式内来使用。
各个表达式操做符的具体使用方式参见:
http://docs.mongodb.org/manual/reference/operator/aggregation-group/
1.$sort + $skip + $limit顺序优化
若是在执行管道聚合时,若是$sort、$skip、$limit依次出现的话,例如:
{ $sort: { age : -1 } },
{ $skip: 10 },
{ $limit: 5 }
那么实际执行的顺序为:
{ $sort: { age : -1 } },
{ $limit: 15 },
{ $skip: 10 }
$limit会提早到$skip前面去执行。
此时$limit = 优化前$skip+优化前$limit
这样作的好处有两个:1.在通过$limit管道后,管道内的文档数量个数会“提早”减少,这样会节省内存,提升内存利用效率。2.$limit提早后,$sort紧邻$limit这样的话,当进行$sort的时候当获得前“$limit”个文档的时候就会中止。
2.$limit + $skip + $limit + $skip Sequence Optimization
若是聚合管道内反复出现下面的聚合序列:
{ $limit: 100 },
{ $skip: 5 },
{ $limit: 10},
{ $skip: 2 }
首先进行局部优化为:能够按照上面所讲的先将第二个$limit提早:
{ $limit: 100 },
{ $limit: 15},
{ $skip: 5 },
{ $skip: 2 }
进一步优化:两个$limit能够直接取最小值 ,两个$skip能够直接相加:
{ $limit: 15 },
{ $skip: 7 }
3.Projection Optimization
过早的使用$project投影,设置须要使用的字段,去掉不用的字段,能够大大减小内存。除此以外也能够过早使用
咱们也应该过早使用$match、$limit、$skip操做符,他们能够提早减小管道内文档数量,减小内存占用,提供聚合效率。
除此以外,$match尽可能放到聚合的第一个阶段,若是这样的话$match至关于一个按条件查询的语句,这样的话可使用索引,加快查询效率。
1.类型限制
在管道内不能操做 Symbol, MinKey, MaxKey, DBRef, Code, CodeWScope类型的数据( 2.4版本解除了对二进制数据的限制).
2.结果大小限制
管道线的输出结果不能超过BSON 文档的大小(16M),若是超出的话会产生错误.
3.内存限制
若是一个管道操做符在执行的过程当中所占有的内存超过系统内存容量的10%的时候,会产生一个错误。
当$sort和$group操做符执行的时候,整个输入都会被加载到内存中,若是这些占有内存超过系统内存的%5的时候,会将一个warning记录到日志文件。一样,所占有的内存超过系统内存容量的10%的时候,会产生一个错误。