對於springframework的mongoTemplate擴充套件自定義的分享
阿新 • • 發佈:2019-02-07
之前對於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雖然語法上和原生的語法有些不同之處,但是它在設計之初充分地考慮到了開發的自定義擴充套件。
上面是我個人的一些小小的心(慘)得(通)經(教)驗(訓),希望能夠對大家有些幫助。