mongo學習使用記錄2 spring data


spring data mongo 打印mongo NoSql語句

log4j.properties

    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 

原因:

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中Mongodb的java實體類映射

spring-data-mongodb中的實體映射是通過MongoMappingConverter這個類實現的。它可以通過注釋把java類轉換為mongodb的文檔。

它有以下幾種注釋:
@Id - 文檔的唯一標識,在mongodb中為ObjectId,它是唯一的,通過時間戳+機器標識+進程ID+自增計數器(確保同一秒內產生的Id不會沖突)構成。

@Document - 把一個java類聲明為mongodb的文檔,可以通過collection參數指定這個類對應的文檔。

@DBRef - 聲明類似於關系數據庫的關聯關系。ps:暫不支持級聯的保存功能,當你在本實例中修改了DERef對象里面的值時,單獨保存本實例並不能保存DERef引用的對象,它要另外保存,如下面例子的Person和Account。

@Indexed - 聲明該字段需要索引,建索引可以大大的提高查詢效率。

@CompoundIndex - 復合索引的聲明,建復合索引可以有效地提高多字段的查詢效率。

@GeoSpatialIndexed - 聲明該字段為地理信息的索引。

@Transient - 映射忽略的字段,該字段不會保存到mongodb。

@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

spring data 4 mongoDB自動創建復合索引
spring data 4 mongodb 在domain上添加annation,自動創建復合索引時需要使用CompoundIndexes。

例如:
 
@CompoundIndex(name = "shop_index", def = "{platform : 1, shopId : 1}") 
程序也不會有編譯錯誤或者執行錯誤,但是spring data不會建立任何索引, 
下面這樣寫才會啟動時自動建立復合索引。 
 
@CompoundIndexes({ 
     @CompoundIndex(name = "shop_index", def = "{platform : 1, shopId : 1}") 
}) 
 
 

spring mongodb中去掉_class列

調用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);

 

 mongo實體設計

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。

spring mongo data api learn

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),而不是先查詢,修改后保存

 

mongo 聚合管道

 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

$addToSet

Returns an array of all the unique values for the selected field among for each document in that group.

$first

Returns the first value in a group.

$last

Returns the last value in a group.

$max

Returns the highest value in a group.

$min

Returns the lowest value in a group.

$avg

Returns an average of all the values in a group.

$push

Returns an array of all values for the selected field among for each document in that group.

$sum

Returns the sum of all the values in a group.

 

Bool類型聚合操作符

Name

Description

$and

Returns true only when all values in its input array are true.

$or

Returns true when any value in its input array are true.

$not

Returns the boolean value that is the opposite of the input value.

 

比較類型聚合操作符

Name

Description

$cmp

Compares two values and returns the result of the comparison as an integer.

$eq

Takes two values and returns true if the values are equivalent.

$gt

Takes two values and returns true if the first is larger than the second.

$gte

Takes two values and returns true if the first is larger than or equal to the second.

$lt

Takes two values and returns true if the second value is larger than the first.

$lte

Takes two values and returns true if the second value is larger than or equal to the first.

$ne

Takes two values and returns true if the values are not equivalent.

 

算術類型聚合操作符

Name

Description

$add

Computes the sum of an array of numbers.

$divide

Takes two numbers and divides the first number by the second.

$mod

Takes two numbers and calcualtes the modulo of the first number divided by the second.

$multiply

Computes the product of an array of numbers.

$subtract

Takes two numbers and subtracts the second number from the first.

 

字符串類型聚合操作符

Name

Description

$concat

Concatenates two strings.

$strcasecmp

Compares two strings and returns an integer that reflects the comparison.

$substr

Takes a string and returns portion of that string.

$toLower

Converts a string to lowercase.

$toUpper

Converts a string to uppercase.

 

日期類型聚合操作符

Name

Description

$dayOfYear

Converts a date to a number between 1 and 366.

$dayOfMonth

Converts a date to a number between 1 and 31.

$dayOfWeek

Converts a date to a number between 1 and 7.

$year

Converts a date to the full year.

$month

Converts a date into a number between 1 and 12.

$week

Converts a date into a number between 0 and 53

$hour

Converts a date into a number between 0 and 23.

$minute

Converts a date into a number between 0 and 59.

$second

Converts a date into a number between 0 and 59. May be 60 to account for leap seconds.

$millisecond

Returns the millisecond portion of a date as an integer between 0 and 999.

 

條件類型聚合操作符

Name

Description

$cond

A ternary operator that evaluates one expression, and depending on the result returns the value of one following expressions.

$ifNull

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM