我想用Java来更简洁地编写MongoDB聚合
可以使用MongoDB中的聚合来实现
虽然比MapReduce快很多,但用Java写确实有点麻烦。如果Java驱动程序有这样的实用程序就好了。我想可能有些开源的东西在某个地方,但我暂时没有找到合适的。所以我简单写了一下。
举例来说,比如使用CLI
db.students.aggregate([
{$unwind:"$scores"},
{$group: {_id:"$name", average:{$avg:"$scores.score"}}},
{$skip:5},
{$limit:3} ])
用Java编写类似的处理
DBObject groupFields = new BasicDBObject("_id", "$name");
groupFields.put("average", new BasicDBObject("$avg", "$scores.score"));
AggregationOutput output = collection.aggregate(
new BasicDBObject("$unwind", "$scores"),
new BasicDBObject("$group", groupFields),
new BasicDBObject("$skip", 5),
new BasicDBObject("$limit", 3));
变成了这样。希望就是
-
- \$groupとか\$avgとか、Stringで渡すのではなく、開発環境でgrとか入力したらgroup?と言って欲しい
- new BasicDBObject を減らしたい
因此,
我创建了两个名为AggBuilder和AggUtils的类。
为了使其更紧凑,省略 import 和 JavaDoc。
public class AggBuilder {
private final DBCollection collection;
private final List<DBObject> ops = new ArrayList<DBObject>();
public AggBuilder(DBCollection collection) {
this.collection = collection;
}
public AggregationOutput execute() {
if (ops.size() > 1) {
return collection.aggregate(ops.remove(0), ops.toArray(new DBObject[ops.size()]));
}
return collection.aggregate(ops.get(0));
}
public AggBuilder limit(int param) {
ops.add(new BasicDBObject("$limit", param));
return this;
}
public AggBuilder skip(int param) {
ops.add(new BasicDBObject("$skip", param));
return this;
}
public AggBuilder unwind(String param) {
ops.add(new BasicDBObject("$unwind", param));
return this;
}
public AggBuilder group(String id, DBObject param) {
DBObject params = new BasicDBObject("_id", id);
params.putAll(param);
ops.add(new BasicDBObject("$group", params));
return this;
}
}
只是将”$什么什么”简单地改为了”什么什么方法”而已。然后,在Utils中加入使得可以在group等情况下使用的操作符进行静态导入。首先只加入了avg。
public class AggUtils {
/**
* @param fieldName
* name of the average field aggregated
* @param fieldToAverage
* field to calculate average for
*/
public static DBObject avg(String fieldName, String fieldToAverage) {
return new BasicDBObject(fieldName, new BasicDBObject("$avg", fieldToAverage));
}
}
使用以上准备好的代码进行聚合,与CLI接近。与之前的JS代码相比,感觉是这样的。唯一需要注意的是,平均值聚合后的名称和平均值操作符的顺序互换了,可能会有点混淆。
db.students.aggregate([
{$unwind:"$scores"},
{$group: {_id:"$name", average:{$avg:"$scores.score"}}},
{$skip:5},
{$limit:3} ])
AggregationOutput output = new AggBuilder(collection)
.unwind("$scores")
.group("$name", avg("average", "$scores.score"))
.skip(5)
.limit(3).execute();
今回はAggregationフレームワークでサポートされている機能の一部を実装したけど、あんまり時間もかからないしgroupをタイプミスしてもコード実行するまで気づかないなんてことがなくなるのは嬉しい。
最后一个示例代码
package advent;
import static advent.AggUtils.avg;
import java.net.UnknownHostException;
import com.mongodb.AggregationOutput;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.MongoClient;
public class AggSample {
public static void main(String[] args) throws UnknownHostException {
MongoClient client = new MongoClient("localhost", 27017);
DB database = client.getDB("school");
DBCollection collection = database.getCollection("students");
/*
* db.students.aggregate([
* {$unwind:"$scores"},
* {$group: {_id:"$name", average:{$avg:"$scores.score"}}},
* {$skip:5},
* {$limit:3} ])
*/
// Normal Java code
DBObject groupFields = new BasicDBObject("_id", "$name");
groupFields.put("average", new BasicDBObject("$avg", "$scores.score"));
printResults(collection.aggregate(
new BasicDBObject("$unwind", "$scores"),
new BasicDBObject("$group", groupFields),
new BasicDBObject("$skip", 5),
new BasicDBObject("$limit", 3)));
// Use AggBuilder
printResults(new AggBuilder(collection)
.unwind("$scores")
.group("$name", avg("average", "$scores.score"))
.skip(5)
.limit(3).execute());
}
private static void printResults(AggregationOutput output) {
for (DBObject obj : output.results()) {
System.out.println(obj);
}
}
}
总结
我想有些人可能已经意识到了,关于\$project方面我没做任何事情。因为这个操作有很高的自由度,所以我没有很容易地想到一个写得漂亮的方法。也许如果仔细搜索的话,会找到有人已经制作了一个涵盖了聚合操作符的工具,但总之,只要自己迅速地写一下,用MongoDB真的很容易,这就是我的想法。