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 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 |
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%的时候,会产生一个错误。