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%的時候,會產生一個錯誤。