DATAMONGO-208 - Add suppoprt for group() operation on collection in MongoOperations
This commit is contained in:
@@ -28,8 +28,11 @@ import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
import org.springframework.data.mongodb.core.geo.GeoResult;
|
||||
import org.springframework.data.mongodb.core.geo.GeoResults;
|
||||
import org.springframework.data.mongodb.core.index.IndexDefinition;
|
||||
import org.springframework.data.mongodb.core.mapreduce.GroupBy;
|
||||
import org.springframework.data.mongodb.core.mapreduce.GroupByResults;
|
||||
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
|
||||
import org.springframework.data.mongodb.core.mapreduce.MapReduceResults;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
@@ -267,7 +270,31 @@ public interface MongoOperations {
|
||||
<T> List<T> findAll(Class<T> entityClass, String collectionName);
|
||||
|
||||
|
||||
/**
|
||||
* Execute a group operation over the entire collection.
|
||||
* The group operation entity class should match the 'shape' of the returned object that takes int account the
|
||||
* initial document structure as well as any finalize functions.
|
||||
*
|
||||
* @param criteria The criteria that restricts the row that are considered for grouping. If not specified all rows are considered.
|
||||
* @param inputCollectionName the collection where the group operation will read from
|
||||
* @param groupBy the conditions under which the group operation will be performed, e.g. keys, initial document, reduce function.
|
||||
* @param entityClass The parameterized type of the returned list
|
||||
* @return The results of the group operation
|
||||
*/
|
||||
<T> GroupByResults<T> group(String inputCollectionName, GroupBy groupBy, Class<T> entityClass);
|
||||
|
||||
/**
|
||||
* Execute a group operation restricting the rows to those which match the provided Criteria.
|
||||
* The group operation entity class should match the 'shape' of the returned object that takes int account the
|
||||
* initial document structure as well as any finalize functions.
|
||||
*
|
||||
* @param criteria The criteria that restricts the row that are considered for grouping. If not specified all rows are considered.
|
||||
* @param inputCollectionName the collection where the group operation will read from
|
||||
* @param groupBy the conditions under which the group operation will be performed, e.g. keys, initial document, reduce function.
|
||||
* @param entityClass The parameterized type of the returned list
|
||||
* @return The results of the group operation
|
||||
*/
|
||||
<T> GroupByResults<T> group(Criteria criteria, String inputCollectionName, GroupBy groupBy, Class<T> entityClass);
|
||||
/**
|
||||
* Execute a map-reduce operation. The map-reduce operation will be formed with an output type of INLINE
|
||||
* @param inputCollectionName the collection where the map-reduce will read from
|
||||
|
||||
@@ -84,8 +84,11 @@ import org.springframework.data.mongodb.core.mapping.event.AfterSaveEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.BeforeSaveEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.MongoMappingEvent;
|
||||
import org.springframework.data.mongodb.core.mapreduce.GroupBy;
|
||||
import org.springframework.data.mongodb.core.mapreduce.GroupByResults;
|
||||
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
|
||||
import org.springframework.data.mongodb.core.mapreduce.MapReduceResults;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
@@ -960,6 +963,75 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
||||
MapReduceResults<T> mapReduceResult = new MapReduceResults<T>(mappedResults, commandResult);
|
||||
return mapReduceResult;
|
||||
|
||||
}
|
||||
|
||||
public <T> GroupByResults<T> group(String inputCollectionName, GroupBy groupBy, Class<T> entityClass) {
|
||||
return group(null, inputCollectionName, groupBy, entityClass);
|
||||
}
|
||||
|
||||
public <T> GroupByResults<T> group(Criteria criteria, String inputCollectionName, GroupBy groupBy, Class<T> entityClass) {
|
||||
|
||||
DBObject dbo = groupBy.getGroupByObject();
|
||||
dbo.put("ns", inputCollectionName);
|
||||
|
||||
if (criteria == null) {
|
||||
dbo.put("cond", null);
|
||||
} else {
|
||||
dbo.put("cond", criteria.getCriteriaObject());
|
||||
}
|
||||
//If initial document was a JavaScript string, potentially loaded by Spring's Resource abstraction, load it and convert to DBObject
|
||||
|
||||
if (dbo.containsField("initial")) {
|
||||
Object initialObj = dbo.get("initial");
|
||||
if (initialObj instanceof String) {
|
||||
String initialAsString = replaceWithResourceIfNecessary((String)initialObj);
|
||||
dbo.put("initial", JSON.parse(initialAsString));
|
||||
}
|
||||
}
|
||||
|
||||
if (dbo.containsField("$reduce")) {
|
||||
dbo.put("$reduce", replaceWithResourceIfNecessary(dbo.get("$reduce").toString()));
|
||||
}
|
||||
if (dbo.containsField("$keyf")) {
|
||||
dbo.put("$keyf", replaceWithResourceIfNecessary(dbo.get("$keyf").toString()));
|
||||
}
|
||||
if (dbo.containsField("finalize")) {
|
||||
dbo.put("finalize", replaceWithResourceIfNecessary(dbo.get("finalize").toString()));
|
||||
}
|
||||
|
||||
DBObject commandObject = new BasicDBObject("group", dbo);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Executing Group with DBObject [" + commandObject.toString() + "]");
|
||||
}
|
||||
CommandResult commandResult = null;
|
||||
try {
|
||||
commandResult = executeCommand(commandObject, getDb().getOptions());
|
||||
commandResult.throwOnError();
|
||||
} catch (RuntimeException ex) {
|
||||
this.potentiallyConvertRuntimeException(ex);
|
||||
}
|
||||
String error = commandResult.getErrorMessage();
|
||||
if (error != null) {
|
||||
throw new InvalidDataAccessApiUsageException("Command execution failed: Error [" + error + "], Command = "
|
||||
+ commandObject);
|
||||
}
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Group command result = [" + commandResult + "]");
|
||||
}
|
||||
|
||||
Iterable<DBObject> resultSet = (Iterable<DBObject>) commandResult.get( "retval" );
|
||||
|
||||
List<T> mappedResults = new ArrayList<T>();
|
||||
DbObjectCallback<T> callback = new ReadDbObjectCallback<T>(mongoConverter, entityClass);
|
||||
for (DBObject dbObject : resultSet) {
|
||||
mappedResults.add(callback.doWith(dbObject));
|
||||
}
|
||||
GroupByResults<T> groupByResult = new GroupByResults<T>(mappedResults, commandResult);
|
||||
return groupByResult;
|
||||
|
||||
|
||||
}
|
||||
|
||||
protected String replaceWithResourceIfNecessary(String function) {
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 2010-2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.mapreduce;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* Collects the parameters required to perform a group operation on a collection. The query condition and the input collection are specified on the group method as method arguments
|
||||
* to be consistent with other operations, e.g. map-reduce.
|
||||
*
|
||||
* @author Mark Pollack
|
||||
*
|
||||
*/
|
||||
public class GroupBy {
|
||||
|
||||
private DBObject dboKeys;
|
||||
|
||||
private String keyFunction;
|
||||
|
||||
private String initial;
|
||||
|
||||
private DBObject initialDbObject;
|
||||
|
||||
private String reduce;
|
||||
|
||||
private String finalize;
|
||||
|
||||
public GroupBy(String... keys) {
|
||||
DBObject dbo = new BasicDBObject();
|
||||
for (String key : keys) {
|
||||
dbo.put(key, 1);
|
||||
}
|
||||
dboKeys = dbo;
|
||||
}
|
||||
|
||||
// NOTE GroupByCommand does not handle keyfunction.
|
||||
|
||||
public GroupBy(String key, boolean isKeyFunction) {
|
||||
DBObject dbo = new BasicDBObject();
|
||||
if (isKeyFunction) {
|
||||
keyFunction = key;
|
||||
} else {
|
||||
dbo.put(key, 1);
|
||||
dboKeys = dbo;
|
||||
}
|
||||
}
|
||||
|
||||
public static GroupBy keyFunction(String key) {
|
||||
return new GroupBy(key, true);
|
||||
}
|
||||
|
||||
public static GroupBy key(String... keys) {
|
||||
return new GroupBy(keys);
|
||||
}
|
||||
|
||||
public GroupBy initialDocument(String initialDocument) {
|
||||
initial = initialDocument;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GroupBy initialDocument(DBObject initialDocument) {
|
||||
initialDbObject = initialDocument;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GroupBy reduceFunction(String reduceFunction) {
|
||||
reduce = reduceFunction;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GroupBy finalizeFunction(String finalizeFunction) {
|
||||
finalize = finalizeFunction;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public DBObject getGroupByObject() {
|
||||
// return new GroupCommand(dbCollection, dboKeys, condition, initial, reduce, finalize);
|
||||
BasicDBObject dbo = new BasicDBObject();
|
||||
if (dboKeys != null) {
|
||||
dbo.put("key", dboKeys);
|
||||
}
|
||||
if (keyFunction != null) {
|
||||
dbo.put("$keyf", keyFunction);
|
||||
}
|
||||
|
||||
dbo.put("$reduce", reduce);
|
||||
|
||||
dbo.put("initial", initialDbObject);
|
||||
if (initial != null) {
|
||||
dbo.put("initial", initial);
|
||||
}
|
||||
if (finalize != null) {
|
||||
dbo.put("finalize", finalize);
|
||||
}
|
||||
return dbo;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.mapreduce;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* Collects the results of executing a group operation.
|
||||
*
|
||||
* @author Mark Pollack
|
||||
*
|
||||
* @param <T> The class in which the results are mapped onto, accessible via an interator.
|
||||
*/
|
||||
public class GroupByResults<T> implements Iterable<T> {
|
||||
|
||||
private final List<T> mappedResults;
|
||||
|
||||
private DBObject rawResults;
|
||||
|
||||
private double count;
|
||||
|
||||
private int keys;
|
||||
|
||||
public GroupByResults(List<T> mappedResults, DBObject rawResults) {
|
||||
Assert.notNull(mappedResults);
|
||||
Assert.notNull(rawResults);
|
||||
this.mappedResults = mappedResults;
|
||||
this.rawResults = rawResults;
|
||||
parseKeys();
|
||||
parseCount();
|
||||
}
|
||||
|
||||
public double getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public int getKeys() {
|
||||
return keys;
|
||||
}
|
||||
|
||||
public Iterator<T> iterator() {
|
||||
return mappedResults.iterator();
|
||||
}
|
||||
|
||||
public DBObject getRawResults() {
|
||||
return rawResults;
|
||||
}
|
||||
|
||||
private void parseCount() {
|
||||
Object object = (Object) rawResults.get("count");
|
||||
if (object instanceof Double) {
|
||||
count = (Double)object;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void parseKeys() {
|
||||
Object object = (Object) rawResults.get("keys");
|
||||
if (object instanceof Integer) {
|
||||
keys = (Integer)object;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,6 +22,12 @@ import org.springframework.util.Assert;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* Collects the results of performing a MapReduce operations.
|
||||
* @author Mark Pollack
|
||||
*
|
||||
* @param <T> The class in which the results are mapped onto, accessible via an interator.
|
||||
*/
|
||||
public class MapReduceResults<T> implements Iterable<T> {
|
||||
|
||||
private final List<T> mappedResults;
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright 2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.mapreduce;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.data.mongodb.MongoDbFactory;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBCollection;
|
||||
import com.mongodb.DBObject;
|
||||
import com.mongodb.Mongo;
|
||||
|
||||
import static org.springframework.data.mongodb.core.query.Criteria.*;
|
||||
import static org.springframework.data.mongodb.core.mapreduce.GroupBy.*;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration("classpath:infrastructure.xml")
|
||||
public class GroupByTests {
|
||||
|
||||
@Autowired
|
||||
MongoDbFactory factory;
|
||||
|
||||
@Autowired
|
||||
ApplicationContext applicationContext;
|
||||
|
||||
//@Autowired
|
||||
//MongoTemplate mongoTemplate;
|
||||
|
||||
|
||||
MongoTemplate mongoTemplate;
|
||||
|
||||
@Autowired
|
||||
@SuppressWarnings("unchecked")
|
||||
public void setMongo(Mongo mongo) throws Exception {
|
||||
|
||||
|
||||
MongoMappingContext mappingContext = new MongoMappingContext();
|
||||
mappingContext.setInitialEntitySet(new HashSet<Class<?>>(Arrays.asList(XObject.class)));
|
||||
mappingContext.afterPropertiesSet();
|
||||
|
||||
MappingMongoConverter mappingConverter = new MappingMongoConverter(factory, mappingContext);
|
||||
mappingConverter.afterPropertiesSet();
|
||||
this.mongoTemplate = new MongoTemplate(factory, mappingConverter);
|
||||
mongoTemplate.setApplicationContext(applicationContext);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
cleanDb();
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanUp() {
|
||||
cleanDb();
|
||||
}
|
||||
|
||||
protected void cleanDb() {
|
||||
mongoTemplate.dropCollection(mongoTemplate.getCollectionName(XObject.class));
|
||||
mongoTemplate.dropCollection("group_test_collection");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void singleKeyCreation() {
|
||||
DBObject gc = new GroupBy("a").getGroupByObject();
|
||||
//String expected = "{ \"group\" : { \"ns\" : \"test\" , \"key\" : { \"a\" : 1} , \"cond\" : null , \"$reduce\" : null , \"initial\" : null }}";
|
||||
String expected = "{ \"key\" : { \"a\" : 1} , \"$reduce\" : null , \"initial\" : null }";
|
||||
Assert.assertEquals(expected, gc.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleKeyCreation() {
|
||||
DBObject gc = GroupBy.key("a","b").getGroupByObject();
|
||||
//String expected = "{ \"group\" : { \"ns\" : \"test\" , \"key\" : { \"a\" : 1 , \"b\" : 1} , \"cond\" : null , \"$reduce\" : null , \"initial\" : null }}";
|
||||
String expected = "{ \"key\" : { \"a\" : 1 , \"b\" : 1} , \"$reduce\" : null , \"initial\" : null }";
|
||||
Assert.assertEquals(expected, gc.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void keyFunctionCreation() {
|
||||
DBObject gc = GroupBy.keyFunction("classpath:keyFunction.js").getGroupByObject();
|
||||
String expected = "{ \"$keyf\" : \"classpath:keyFunction.js\" , \"$reduce\" : null , \"initial\" : null }";
|
||||
Assert.assertEquals(expected, gc.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void SimpleGroup() {
|
||||
createGroupByData();
|
||||
GroupByResults<XObject> results;
|
||||
|
||||
results = mongoTemplate.group("group_test_collection",
|
||||
GroupBy.key("x").initialDocument(new BasicDBObject("count", 0)).reduceFunction("function(doc, prev) { prev.count += 1 }"), XObject.class);
|
||||
|
||||
assertMapReduceResults(results);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void SimpleGroupWithKeyFunction() {
|
||||
createGroupByData();
|
||||
GroupByResults<XObject> results;
|
||||
|
||||
results = mongoTemplate.group("group_test_collection",
|
||||
GroupBy.keyFunction("function(doc) { return { x : doc.x }; }").initialDocument("{ count: 0 }").reduceFunction("function(doc, prev) { prev.count += 1 }"), XObject.class);
|
||||
|
||||
assertMapReduceResults(results);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void SimpleGroupWithFunctionsAsResources() {
|
||||
createGroupByData();
|
||||
GroupByResults<XObject> results;
|
||||
|
||||
results = mongoTemplate.group("group_test_collection",
|
||||
GroupBy.keyFunction("classpath:keyFunction.js").initialDocument("{ count: 0 }").reduceFunction("classpath:groupReduce.js"), XObject.class);
|
||||
|
||||
assertMapReduceResults(results);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void SimpleGroupWithQueryAndFunctionsAsResources() {
|
||||
createGroupByData();
|
||||
GroupByResults<XObject> results;
|
||||
|
||||
results = mongoTemplate.group(where("x").gt(0),
|
||||
"group_test_collection",
|
||||
keyFunction("classpath:keyFunction.js").initialDocument("{ count: 0 }").reduceFunction("classpath:groupReduce.js"), XObject.class);
|
||||
|
||||
assertMapReduceResults(results);
|
||||
}
|
||||
|
||||
|
||||
private void assertMapReduceResults(GroupByResults<XObject> results) {
|
||||
DBObject dboRawResults = results.getRawResults();
|
||||
String expected = "{ \"retval\" : [ { \"x\" : 1.0 , \"count\" : 2.0} , { \"x\" : 2.0 , \"count\" : 1.0} , { \"x\" : 3.0 , \"count\" : 3.0}] , \"count\" : 6.0 , \"keys\" : 3 , \"ok\" : 1.0}";
|
||||
Assert.assertEquals(expected, dboRawResults.toString());
|
||||
|
||||
int numResults = 0;
|
||||
for (XObject xObject : results) {
|
||||
if (xObject.getX() == 1) {
|
||||
Assert.assertEquals(2, xObject.getCount(), 0.001);
|
||||
}
|
||||
if (xObject.getX() == 2) {
|
||||
Assert.assertEquals(1, xObject.getCount(), 0.001);
|
||||
}
|
||||
if (xObject.getX() == 3) {
|
||||
Assert.assertEquals(3, xObject.getCount(), 0.001);
|
||||
}
|
||||
numResults++;
|
||||
}
|
||||
Assert.assertEquals(3, numResults);
|
||||
Assert.assertEquals(6, results.getCount(), 0.001);
|
||||
Assert.assertEquals(3, results.getKeys());
|
||||
}
|
||||
|
||||
|
||||
private void createGroupByData() {
|
||||
DBCollection c = mongoTemplate.getDb().getCollection("group_test_collection");
|
||||
c.save(new BasicDBObject("x", 1));
|
||||
c.save(new BasicDBObject("x", 1));
|
||||
c.save(new BasicDBObject("x", 2));
|
||||
c.save(new BasicDBObject("x", 3));
|
||||
c.save(new BasicDBObject("x", 3));
|
||||
c.save(new BasicDBObject("x", 3));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -60,6 +60,7 @@ public class MapReduceTests {
|
||||
MongoTemplate template;
|
||||
@Autowired
|
||||
MongoDbFactory factory;
|
||||
|
||||
MongoTemplate mongoTemplate;
|
||||
|
||||
@Autowired
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.springframework.data.mongodb.core.mapreduce;
|
||||
|
||||
public class XObject {
|
||||
|
||||
private float x;
|
||||
|
||||
private float count;
|
||||
|
||||
|
||||
public float getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
public void setX(float x) {
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
|
||||
public float getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
public void setCount(float count) {
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "XObject [x=" + x + " count = " + count + "]";
|
||||
}
|
||||
|
||||
}
|
||||
1
spring-data-mongodb/src/test/resources/groupReduce.js
Normal file
1
spring-data-mongodb/src/test/resources/groupReduce.js
Normal file
@@ -0,0 +1 @@
|
||||
function(doc, prev) { prev.count += 1 }
|
||||
1
spring-data-mongodb/src/test/resources/keyFunction.js
Normal file
1
spring-data-mongodb/src/test/resources/keyFunction.js
Normal file
@@ -0,0 +1 @@
|
||||
function(doc) { return { x : doc.x }; }
|
||||
@@ -1451,8 +1451,8 @@ import static org.springframework.data.mongodb.core.query.Query.query;
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para><literal>Criteria</literal> <emphasis role="bold">andOperator
|
||||
</emphasis> <literal>(Criteria...
|
||||
<para><literal>Criteria</literal> <emphasis
|
||||
role="bold">andOperator </emphasis> <literal>(Criteria...
|
||||
criteria)</literal>Creates an and query using the
|
||||
<literal>$and</literal> operator for all of the provided
|
||||
criteria (requires MongoDB 2.0 or later)</para>
|
||||
@@ -1535,8 +1535,8 @@ import static org.springframework.data.mongodb.core.query.Query.query;
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para><literal>Criteria</literal> <emphasis role="bold">norOperator
|
||||
</emphasis> <literal>(Criteria...
|
||||
<para><literal>Criteria</literal> <emphasis
|
||||
role="bold">norOperator </emphasis> <literal>(Criteria...
|
||||
criteria)</literal>Creates an nor query using the
|
||||
<literal>$nor</literal> operator for all of the provided
|
||||
criteria</para>
|
||||
@@ -1550,8 +1550,8 @@ import static org.springframework.data.mongodb.core.query.Query.query;
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para><literal>Criteria</literal> <emphasis role="bold">orOperator
|
||||
</emphasis> <literal>(Criteria...
|
||||
<para><literal>Criteria</literal> <emphasis
|
||||
role="bold">orOperator </emphasis> <literal>(Criteria...
|
||||
criteria)</literal>Creates an or query using the
|
||||
<literal>$or</literal> operator for all of the provided
|
||||
criteria</para>
|
||||
@@ -1847,22 +1847,24 @@ GeoResults<Restaurant> = operations.geoNear(query, Restaurant.class);</pro
|
||||
</section>
|
||||
|
||||
<section id="mongo.mapreduce">
|
||||
<title>Map-Reduce</title>
|
||||
<title>Map-Reduce Operations</title>
|
||||
|
||||
<para>You can query MongoDB using Map-Reduce which is useful for batch
|
||||
processing, data aggregation, and for when the query language doesn't
|
||||
fulfill your needs. Spring provides integration with MongoDB's map reduce
|
||||
by providing methods on MongoOperations to simplify the creation and
|
||||
execution of Map-Reduce operations. It also integrates with Spring's
|
||||
<ulink
|
||||
fulfill your needs.</para>
|
||||
|
||||
<para>Spring provides integration with MongoDB's map reduce by providing
|
||||
methods on MongoOperations to simplify the creation and execution of
|
||||
Map-Reduce operations. It can convert the results of a Map-Reduce
|
||||
operation to a POJO also integrates with Spring's <ulink
|
||||
url="http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/resources.html">Resource
|
||||
abstraction</ulink> abstraction. This will let you place your JavaScript
|
||||
files on the file system, classpath, http server or any other Spring
|
||||
Resource implementation and then reference the JavaScript resources via an
|
||||
easy URI style syntax, e.g. 'classpath:reduce.js;. Externalizing
|
||||
JavaScript code in files is preferable to embedding them as Java strings
|
||||
in your code. You can still pass JavaScript code as Java strings if you
|
||||
prefer.</para>
|
||||
JavaScript code in files is often preferable to embedding them as Java
|
||||
strings in your code. Note that you can still pass JavaScript code as Java
|
||||
strings if you prefer.</para>
|
||||
|
||||
<section id="mongo.mapreduce.example" lang="">
|
||||
<title>Example Usage</title>
|
||||
@@ -1967,6 +1969,138 @@ MapReduceResults<ValueObject> results = mongoOperations.mapReduce(query, "
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section id="mongo.group">
|
||||
<title>Group Operations</title>
|
||||
|
||||
<para>As an alternative to usiing Map-Reduce to perform data aggregation,
|
||||
you can use the <ulink
|
||||
url="http://www.mongodb.org/display/DOCS/Aggregation#Aggregation-Group"><literal>group</literal>
|
||||
operation</ulink> which feels similar to using SQL's group by query style,
|
||||
so it may feel more approachable vs. using Map-Reduce. Using the group
|
||||
operations does have some limitations, for example it is not supported in
|
||||
a shareded environment and it returns the full result set in a single BSON
|
||||
object, so the result should be small, less than 10,000 keys. </para>
|
||||
|
||||
<para>Spring provides integration with MongoDB's group operation by
|
||||
providing methods on MongoOperations to simplify the creation and
|
||||
execution of group operations. It can convert the results of the group
|
||||
operation to a POJO and also integrates with Spring's <ulink
|
||||
url="http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/resources.html">Resource
|
||||
abstraction</ulink> abstraction. This will let you place your JavaScript
|
||||
files on the file system, classpath, http server or any other Spring
|
||||
Resource implementation and then reference the JavaScript resources via an
|
||||
easy URI style syntax, e.g. 'classpath:reduce.js;. Externalizing
|
||||
JavaScript code in files if often preferable to embedding them as Java
|
||||
strings in your code. Note that you can still pass JavaScript code as Java
|
||||
strings if you prefer.</para>
|
||||
|
||||
<section>
|
||||
<title>Example Usage</title>
|
||||
|
||||
<para>In order to understand how group operations work the following
|
||||
example is used, which is somewhat artifical. For a more realistic
|
||||
example consult the book 'MongoDB - The definitive guide'. A collection
|
||||
named "group_test_collection" created with the following rows.</para>
|
||||
|
||||
<programlisting>{ "_id" : ObjectId("4ec1d25d41421e2015da64f1"), "x" : 1 }
|
||||
{ "_id" : ObjectId("4ec1d25d41421e2015da64f2"), "x" : 1 }
|
||||
{ "_id" : ObjectId("4ec1d25d41421e2015da64f3"), "x" : 2 }
|
||||
{ "_id" : ObjectId("4ec1d25d41421e2015da64f4"), "x" : 3 }
|
||||
{ "_id" : ObjectId("4ec1d25d41421e2015da64f5"), "x" : 3 }
|
||||
{ "_id" : ObjectId("4ec1d25d41421e2015da64f6"), "x" : 3 }</programlisting>
|
||||
|
||||
<para>We would like to group by the only field in each row, the 'x'
|
||||
field and aggregate the number of times each specific value of 'x'
|
||||
occurs. To do this we need to create an initial document that contains
|
||||
our count variable and also a reduce function which will increment it
|
||||
each time it is encountered. The Java code to execute the group
|
||||
operation is shown below</para>
|
||||
|
||||
<programlisting language="java">GroupByResults<XObject> results = mongoTemplate.group("group_test_collection",
|
||||
GroupBy.key("x").initialDocument("{ count: 0 }").reduceFunction("function(doc, prev) { prev.count += 1 }"),
|
||||
XObject.class);</programlisting>
|
||||
|
||||
<para>The first argument is the name of the collection to run the group
|
||||
operation over, the second is a fluent API that specifies properties of
|
||||
the group operation via a <classname>GroupBy</classname> class. In this
|
||||
example we are using just the <methodname>intialDocument</methodname>
|
||||
and <methodname>reduceFunction</methodname> methods. You can also
|
||||
specify a key-function, as well as a finalizer as part of the fluent
|
||||
API. If you have multiple keys to group by, you can pass in a comma
|
||||
separated list of keys.</para>
|
||||
|
||||
<para>The raw results of the group operation is a JSON document that
|
||||
looks like this</para>
|
||||
|
||||
<programlisting>{
|
||||
"retval" : [ { "x" : 1.0 , "count" : 2.0} ,
|
||||
{ "x" : 2.0 , "count" : 1.0} ,
|
||||
{ "x" : 3.0 , "count" : 3.0} ] ,
|
||||
"count" : 6.0 ,
|
||||
"keys" : 3 ,
|
||||
"ok" : 1.0
|
||||
}</programlisting>
|
||||
|
||||
<para>The document under the "retval" field is mapped onto the third
|
||||
argument in the group method, in this case XObject which is shown
|
||||
below.</para>
|
||||
|
||||
<programlisting>public class XObject {
|
||||
|
||||
private float x;
|
||||
|
||||
private float count;
|
||||
|
||||
|
||||
public float getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
public void setX(float x) {
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
|
||||
public float getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
public void setCount(float count) {
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "XObject [x=" + x + " count = " + count + "]";
|
||||
}
|
||||
|
||||
}</programlisting>
|
||||
|
||||
<para>You can also obtain tha raw result as a
|
||||
<classname>DbObject</classname> by calling the method
|
||||
<methodname>getRawResults</methodname> on the
|
||||
<classname>GroupByResults</classname> class.</para>
|
||||
|
||||
<para>There is an additional method overload of the group method on
|
||||
<interfacename>MongoOperations</interfacename> which lets you specify a
|
||||
<classname>Criteria</classname> object for selecting a subset of the
|
||||
rows. An example which uses a <classname>Criteria</classname> object,
|
||||
with some syntax sugar using static imports, as well as referencing a
|
||||
key-function and reduce function javascript files via a Spring Resource
|
||||
string is shown below.</para>
|
||||
|
||||
<programlisting>import static org.springframework.data.mongodb.core.mapreduce.GroupBy.keyFunction;
|
||||
import static org.springframework.data.mongodb.core.query.Criteria.where;
|
||||
|
||||
GroupByResults<XObject> results = mongoTemplate.group(where("x").gt(0),
|
||||
"group_test_collection",
|
||||
keyFunction("classpath:keyFunction.js").initialDocument("{ count: 0 }").reduceFunction("classpath:groupReduce.js"), XObject.class);</programlisting>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Overriding default mapping with custom converters</title>
|
||||
|
||||
@@ -2356,4 +2490,4 @@ mongoTemplate.dropCollection("MyNewCollection"); </programlisting>
|
||||
}
|
||||
});</programlisting>
|
||||
</section>
|
||||
</chapter>
|
||||
</chapter>
|
||||
|
||||
Reference in New Issue
Block a user