之前对于sPRing的mongoTemplate真的是有点又爱又恨,由于它对mongodb的驱动做了一层封装,使得在开发的时候方便了许多,但是它的语法和mongo的原生js有很大不同,有时候在mongo官方文档里的API接口很多时候在mongoTemplate中的使用完全不一样,导致有些时候用的很别扭,而且一些语句完全不知道怎么去转换为template的语法。不过最近的两次使用经历使得我对mongoTemplate有了一些改观。
第一个就是mongoTemplate自身的criteria.where没有>和<的操作,也就是对mongo的一条记录自身的两个字段进行比较。mongo语句如下:
db.whereColl.find({$where: "this.b > this.a"})当时研究和许久,最后实在没招了,扒源码找出了它的Criteria实现,下面是它的源码实现:
/** * Creates a criterion using the {@literal $elemMatch} Operator * * @see http://docs.mongodb.org/manual/reference/operator/query/elemMatch/ * @param c * @return */public Criteria elemMatch(Criteria c) { criteria.put("$elemMatch", c.getCriteriaObject()); return this;}/** * Creates an 'and' criteria using the $and operator for all of the provided criteria. * <p> * Note that mongodb doesn't support an $and operator to be wrapped in a $not operator. * <p> * * @throws IllegalArgumentException if {@link #andOperator(Criteria...)} follows a not() call directly. * @param criteria */public Criteria andOperator(Criteria... criteria) { BasicDBList bsonList = createCriteriaList(criteria); return registerCriteriaChainElement(new Criteria("$and").is(bsonList));}private BasicDBList createCriteriaList(Criteria[] criteria) { BasicDBList bsonList = new BasicDBList(); for (Criteria c : criteria) { bsonList.add(c.getCriteriaObject()); } return bsonList;}public DBObject getCriteriaObject() { if (this.criteriaChain.size() == 1) { return criteriaChain.get(0).getSingleCriteriaObject(); } else if (CollectionUtils.isEmpty(this.criteriaChain) && !CollectionUtils.isEmpty(this.criteria)) { return getSingleCriteriaObject(); } else { DBObject criteriaObject = new BasicDBObject(); for (Criteria c : this.criteriaChain) { DBObject dbo = c.getSingleCriteriaObject(); for (String k : dbo.keySet()) { setValue(criteriaObject, k, dbo.get(k)); } } return criteriaObject; }}protected DBObject getSingleCriteriaObject() { DBObject dbo = new BasicDBObject(); boolean not = false; for (String k : this.criteria.keySet()) { Object value = this.criteria.get(k); if (not) { DBObject notDbo = new BasicDBObject(); notDbo.put(k, value); dbo.put("$not", notDbo); not = false; } else { if ("$not".equals(k) && value == null) { not = true; } else { dbo.put(k, value); } } } if (!StringUtils.hasText(this.key)) { if (not) { return new BasicDBObject("$not", dbo); } return dbo; } DBObject queryCriteria = new BasicDBObject(); if (!NOT_SET.equals(isValue)) { queryCriteria.put(this.key, this.isValue); queryCriteria.putAll(dbo); } else { queryCriteria.put(this.key, dbo); } return queryCriteria;}private void setValue(DBObject dbo, String key, Object value) { Object existing = dbo.get(key); if (existing == null) { dbo.put(key, value); } else { throw new InvalidMongoDbApiUsageException("Due to limitations of the com.mongodb.BasicDBObject, " + "you can't add a second '" + key + "' expression specified as '" + key + " : " + value + "'. " + "Criteria already contains '" + key + " : " + existing + "'."); }}通过上面的源码可以知道,mongoTemplate的语法转换是通过构建一个DBObject,然后将查询语法转换为mongo的java驱动的语法,也是接近于原生的语法。知道了这个,稍微对com.mongodb包有些经验的都应该知道要怎么去操作了,我们通过复写getCriteriaObject方法来实现自定义查询语句,突破mongoTemplate的限制,我的实现如下:
public ListResponse<AObject> loadAObjectList(String aId, String bId, int start, int limit) { ListResponse<AObject> AObjectListResp = new ListResponse<AObject>(); Criteria criteria = new Criteria() { @Override public DBObject getCriteriaObject() { DBObject obj = new BasicDBObject(); obj.put("$where", "this.groupNum > this.joinedNum"); return obj; } }; Query query = Query.query(criteria); query.addCriteria(Criteria.where("members").nin(bId).and("aId").is(aId)).with(new Sort(Sort.Direction.DESC, "gmtCreated")).skip((start-1) * limit).limit(limit); List<AObject> AObjectList = template.find(query, AObject.class); long count = template.count(new Query(criteria).addCriteria(.where("members").nin(bId).and("aId").is(aId)), AObject.class); return AObjectListResp.fill(ResponseCode.SUCCESS, "success", AObjectList, count, count > start * limit); }上面我们通过复写相关方法来自定义Criteria语法,而另一个事件是今天的一个项目,需要从mongo中随机取出一定数量的文档,我们知道mongo在3.2版本之后对此加入了一个官方的api,我们可以使用aggregate管道的$sample来读取文件,mongo语法如下:db.users.aggregate( [ { $sample: { size: 3 } } ])我看了mongotemple的aggregate相关API,发现它的API只有以下几个,即便是升级到最新的1.10.0-RELEASE版本也是如此,最后我想起上面的自定义扩展,于是,又去吭哧吭哧地翻看源代码,首先:template.aggregate(Aggregation.newAggregation(Aggregation.match(Criteria),.....),....);我发现它的语法条件都是通过Aggregation.XXX来操作的,于是,我打开Aggregation.XXX的源码:/** * Creates a new {@link MatchOperation} using the given {@link Criteria}. * * @param criteria must not be {@literal null}. * @return */public static MatchOperation match(Criteria criteria) { return new MatchOperation(criteria);}/** * Creates a new {@link LimitOperation} limiting the result to the given number of elements. * * @param maxElements must not be less than zero. * @return */public static LimitOperation limit(long maxElements) { return new LimitOperation(maxElements);}我发现它是构建不同的XXXOperation,于是,我打开MatchOperation和LimitOperation:public class MatchOperation implements AggregationOperation { private final CriteriaDefinition criteriaDefinition; /** * Creates a new {@link MatchOperation} for the given {@link CriteriaDefinition}. * * @param criteriaDefinition must not be {@literal null}. */ public MatchOperation(CriteriaDefinition criteriaDefinition) { Assert.notNull(criteriaDefinition, "Criteria must not be null!"); this.criteriaDefinition = criteriaDefinition; } /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) */ @Override public DBObject toDBObject(AggregationOperationContext context) { return new BasicDBObject("$match", context.getMappedObject(criteriaDefinition.getCriteriaObject())); }}public class LimitOperation implements AggregationOperation { private final long maxElements; /** * @param maxElements Number of documents to consider. */ public LimitOperation(long maxElements) { Assert.isTrue(maxElements >= 0, "Maximum number of elements must be greater or equal to zero!"); this.maxElements = maxElements; } /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) */ @Override public DBObject toDBObject(AggregationOperationContext context) { return new BasicDBObject("$limit", maxElements); }}到这儿,我发现最终,它的操作也是落到了BasicDBObject上面,并且都是通过继承AggregationOperation来实现的,于是我照着葫芦画瓢,自己自定义了一个SampleOperation类:public class SampleOperation implements AggregationOperation { private final long maxElements; /** * @param maxElements Number of documents to consider. */ public SampleOperation(long maxElements) { Assert.isTrue(maxElements >= 0, "Maximum number of elements must be greater or equal to zero!"); this.maxElements = maxElements; } /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) */ @Override public DBObject toDBObject(AggregationOperationContext context) { return new BasicDBObject("$sample", new BasicDBObject("size", maxElements)); }}通过这两次经历,我发现mongoTemplate虽然语法上和原生的语法有些不同之处,但是它在设计之初充分地考虑到了开发的自定义扩展。上面是我个人的一些小小的心(惨)得(通)经(教)验(训),希望能够对大家有些帮助。
新闻热点
疑难解答