Browse Source

DATAMONGO-208 - Add suppoprt for group() operation on collection in MongoOperations

pull/1/head
Mark Pollack 14 years ago
parent
commit
3e15c21419
  1. 27
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java
  2. 72
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
  3. 118
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/GroupBy.java
  4. 82
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/GroupByResults.java
  5. 6
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/MapReduceResults.java
  6. 196
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/GroupByTests.java
  7. 1
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceTests.java
  8. 35
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/XObject.java
  9. 1
      spring-data-mongodb/src/test/resources/groupReduce.js
  10. 1
      spring-data-mongodb/src/test/resources/keyFunction.js
  11. 164
      src/docbkx/reference/mongodb.xml

27
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java

@ -28,8 +28,11 @@ import org.springframework.data.mongodb.core.convert.MongoConverter; @@ -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 { @@ -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

72
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java

@ -84,8 +84,11 @@ import org.springframework.data.mongodb.core.mapping.event.AfterSaveEvent; @@ -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 { @@ -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) {

118
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/GroupBy.java

@ -0,0 +1,118 @@ @@ -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;
}
}

82
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/GroupByResults.java

@ -0,0 +1,82 @@ @@ -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;
}
}
}

6
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/MapReduceResults.java

@ -22,6 +22,12 @@ import org.springframework.util.Assert; @@ -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;

196
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/GroupByTests.java

@ -0,0 +1,196 @@ @@ -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));
}
}

1
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceTests.java

@ -60,6 +60,7 @@ public class MapReduceTests { @@ -60,6 +60,7 @@ public class MapReduceTests {
MongoTemplate template;
@Autowired
MongoDbFactory factory;
MongoTemplate mongoTemplate;
@Autowired

35
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/XObject.java

@ -0,0 +1,35 @@ @@ -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

@ -0,0 +1 @@ @@ -0,0 +1 @@
function(doc, prev) { prev.count += 1 }

1
spring-data-mongodb/src/test/resources/keyFunction.js

@ -0,0 +1 @@ @@ -0,0 +1 @@
function(doc) { return { x : doc.x }; }

164
src/docbkx/reference/mongodb.xml

@ -1451,8 +1451,8 @@ import static org.springframework.data.mongodb.core.query.Query.query; @@ -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; @@ -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; @@ -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&lt;Restaurant&gt; = operations.geoNear(query, Restaurant.class);</pro @@ -1847,22 +1847,24 @@ GeoResults&lt;Restaurant&gt; = 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&lt;ValueObject&gt; results = mongoOperations.mapReduce(query, " @@ -1967,6 +1969,138 @@ MapReduceResults&lt;ValueObject&gt; 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&lt;XObject&gt; 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&lt;XObject&gt; 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> @@ -2356,4 +2490,4 @@ mongoTemplate.dropCollection("MyNewCollection"); </programlisting>
}
});</programlisting>
</section>
</chapter>
</chapter>

Loading…
Cancel
Save