diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DocumentCallbackHandler.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DocumentCallbackHandler.java new file mode 100644 index 000000000..c1bac4b22 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DocumentCallbackHandler.java @@ -0,0 +1,37 @@ +/* + * 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; + +import org.springframework.dao.DataAccessException; + +import com.mongodb.DBObject; +import com.mongodb.MongoException; + +/** + * An interface used by {@link MongoTemplate} for processing documents returned from a MongoDB query on a per-document basis. + * Implementations of this interface perform the actual work of prcoessing each document but don't need to worry about + * exception handling. {@MongoException}s will be caught and translated by the calling MongoTemplate + * + * An DocumentCallbackHandler is typically stateful: It keeps the result state within the object, to be available later for later + * inspection. + * + * @author Mark Pollack + * + */ +public interface DocumentCallbackHandler { + + void processDocument(DBObject dbObject) throws MongoException, DataAccessException; +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java index 008047928..46c27bda2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java @@ -66,6 +66,28 @@ public interface MongoOperations { * @param command a MongoDB command */ CommandResult executeCommand(DBObject command); + + /** + * Execute a MongoDB query and iterate over the query results on a per-document basis with a DocumentCallbackHandler. + * + * @param query the query class that specifies the criteria used to find a record and also an optional fields + * specification + * @param collectionName name of the collection to retrieve the objects from + * @param dch the handler that will extract results, one document at a time + */ + void executeQuery(Query query, String collectionName, DocumentCallbackHandler dch); + + /** + * Execute a MongoDB query and iterate over the query results on a per-document basis with a DocumentCallbackHandler using the + * provided CursorPreparer. + * @param query the query class that specifies the criteria used to find a record and also an optional fields + * specification + * @param collectionName name of the collection to retrieve the objects from + * @param dch the handler that will extract results, one document at a time + * @param preparer allows for customization of the DBCursor used when iterating over the result set, (apply limits, + * skips and so on). + */ + void executeQuery(Query query, String collectionName, DocumentCallbackHandler dch, CursorPreparer preparer); /** * Executes a {@link DbCallback} translating any exceptions as necessary. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index 37a023d57..e8a463f44 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -265,6 +265,19 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { return result; } + public void executeQuery(Query query, String collectionName, DocumentCallbackHandler dch) { + executeQuery(query, collectionName, dch, null); + } + + public void executeQuery(Query query, String collectionName, DocumentCallbackHandler dch, CursorPreparer preparer) { + DBObject queryObject = query.getQueryObject(); + DBObject fieldsObject = query.getFieldsObject(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("find using query: " + queryObject + " fields: " + fieldsObject + " in collection: " + collectionName); + } + this.executeQueryInternal(new FindCallback(queryObject, fieldsObject), preparer, dch, collectionName); + } + public T execute(DbCallback action) { Assert.notNull(action); @@ -1079,6 +1092,24 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { throw potentiallyConvertRuntimeException(e); } } + + private void executeQueryInternal(CollectionCallback collectionCallback, + CursorPreparer preparer, DocumentCallbackHandler callbackHandler, String collectionName) { + + try { + DBCursor cursor = collectionCallback.doInCollection(getAndPrepareCollection(getDb(), collectionName)); + + if (preparer != null) { + cursor = preparer.prepare(cursor); + } + + for (DBObject dbobject : cursor) { + callbackHandler.processDocument(dbobject); + } + } catch (RuntimeException e) { + throw potentiallyConvertRuntimeException(e); + } + } private MongoPersistentEntity getPersistentEntity(Class type) { return type == null ? null : mappingContext.getPersistentEntity(type); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java index bf5a89e50..0c91924ce 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java @@ -27,6 +27,7 @@ import java.util.HashSet; import java.util.List; import org.bson.types.ObjectId; +import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; @@ -50,6 +51,7 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.mongodb.DBCollection; +import com.mongodb.DBCursor; import com.mongodb.DBObject; import com.mongodb.DBRef; import com.mongodb.Mongo; @@ -58,7 +60,7 @@ import com.mongodb.WriteResult; /** * Integration test for {@link MongoTemplate}. - * + * * @author Oliver Gierke * @author Thomas Risberg */ @@ -92,8 +94,17 @@ public class MongoTemplateTests { this.mappingTemplate = new MongoTemplate(factory, mappingConverter); } - @Before + @Before public void setUp() { + cleanDb(); + } + + @After + public void cleanUp() { + cleanDb(); + } + + protected void cleanDb() { template.dropCollection(template.getCollectionName(Person.class)); template.dropCollection(template.getCollectionName(PersonWith_idPropertyOfTypeObjectId.class)); template.dropCollection(template.getCollectionName(PersonWith_idPropertyOfTypeString.class)); @@ -112,8 +123,7 @@ public class MongoTemplateTests { person.setAge(25); template.insert(person); - List result = template.find(new Query(Criteria.where("_id").is(person.getId())), - Person.class); + List result = template.find(new Query(Criteria.where("_id").is(person.getId())), Person.class); assertThat(result.size(), is(1)); assertThat(result, hasItem(person)); } @@ -173,7 +183,7 @@ public class MongoTemplateTests { } private void testProperHandlingOfDifferentIdTypes(MongoTemplate mongoTemplate) throws Exception { - + // String id - generated PersonWithIdPropertyOfTypeString p1 = new PersonWithIdPropertyOfTypeString(); p1.setFirstName("Sven_1"); @@ -366,7 +376,7 @@ public class MongoTemplateTests { private void checkCollectionContents(Class entityClass, int count) { assertThat(template.findAll(entityClass).size(), is(count)); } - + @Test public void testFindAndRemove() throws Exception { @@ -727,8 +737,7 @@ public class MongoTemplateTests { @Test public void testUsingSlaveOk() throws Exception { this.template.execute("slaveOkTest", new CollectionCallback() { - public Object doInCollection(DBCollection collection) - throws MongoException, DataAccessException { + public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException { assertThat(collection.getOptions(), is(0)); assertThat(collection.getDB().getOptions(), is(0)); return null; @@ -737,8 +746,7 @@ public class MongoTemplateTests { MongoTemplate slaveTemplate = new MongoTemplate(factory); slaveTemplate.setSlaveOk(true); slaveTemplate.execute("slaveOkTest", new CollectionCallback() { - public Object doInCollection(DBCollection collection) - throws MongoException, DataAccessException { + public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException { assertThat(collection.getOptions(), is(4)); assertThat(collection.getDB().getOptions(), is(0)); return null; @@ -759,16 +767,17 @@ public class MongoTemplateTests { */ @Test public void updatesObjectIdsCorrectly() { - + PersonWithIdPropertyOfTypeObjectId person = new PersonWithIdPropertyOfTypeObjectId(); person.setId(new ObjectId()); person.setFirstName("Dave"); - + template.save(person); template.updateFirst(query(where("id").is(person.getId())), update("firstName", "Carter"), PersonWithIdPropertyOfTypeObjectId.class); - - PersonWithIdPropertyOfTypeObjectId result = template.findById(person.getId(), PersonWithIdPropertyOfTypeObjectId.class); + + PersonWithIdPropertyOfTypeObjectId result = template.findById(person.getId(), + PersonWithIdPropertyOfTypeObjectId.class); assertThat(result, is(notNullValue())); assertThat(result.getId(), is(person.getId())); assertThat(result.getFirstName(), is("Carter")); @@ -779,14 +788,63 @@ public class MongoTemplateTests { */ @Test public void updatesDBRefsCorrectly() { - + DBRef first = new DBRef(factory.getDb(), "foo", new ObjectId()); DBRef second = new DBRef(factory.getDb(), "bar", new ObjectId()); - + template.updateFirst(null, Update.update("dbRefs", Arrays.asList(first, second)), ClassWithDBRefs.class); } - + class ClassWithDBRefs { List dbrefs; } + + /** + * @see DATADOC-202 + */ + @Test + public void executeDocument() { + template.insert(new Person("Tom")); + template.insert(new Person("Dick")); + template.insert(new Person("Harry")); + final List names = new ArrayList(); + template.executeQuery(new Query(), template.getCollectionName(Person.class), new DocumentCallbackHandler() { + public void processDocument(DBObject dbObject) { + String name = (String) dbObject.get("firstName"); + if (name != null) { + names.add(name); + } + } + }); + assertEquals(3, names.size()); + //template.remove(new Query(), Person.class); + } + + /** + * @see DATADOC-202 + */ + @Test + public void executeDocumentWithCursorPreparer() { + template.insert(new Person("Tom")); + template.insert(new Person("Dick")); + template.insert(new Person("Harry")); + final List names = new ArrayList(); + template.executeQuery(new Query(), template.getCollectionName(Person.class), new DocumentCallbackHandler() { + public void processDocument(DBObject dbObject) { + String name = (String) dbObject.get("firstName"); + if (name != null) { + names.add(name); + } + } + }, new CursorPreparer() { + + public DBCursor prepare(DBCursor cursor) { + cursor.limit(1); + return cursor; + } + + }); + assertEquals(1, names.size()); + //template.remove(new Query(), Person.class); + } }