Browse Source

DATADOC-22 - Added pagination and sorting to repository and finders.

- added CursorCallback to be able to apply limit, skip and sorting to the query execution
- added paged execution for queries and extended SimpleMongoRepository to support paging and sorting as well
- minor JavaDoc polishing.
pull/1/head
Oliver Gierke 15 years ago
parent
commit
72a71166e0
  1. 34
      spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/CursorPreparer.java
  2. 47
      spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoTemplate.java
  3. 153
      spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoCursorUtils.java
  4. 112
      spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoQuery.java
  5. 3
      spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoRepository.java
  6. 46
      spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/SimpleMongoRepository.java
  7. 8
      spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/Person.java
  8. 27
      spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/PersonRepository.java
  9. 65
      spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/PersonRepositoryIntegrationTests.java

34
spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/CursorPreparer.java

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
/*
* Copyright 2002-2010 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.document.mongodb;
import com.mongodb.DBCursor;
/**
* Simple callback interface to allow customization of a {@link DBCursor}.
*
* @author Oliver Gierke
*/
public interface CursorPreparer {
/**
* Prepare the given cursor (apply limits, skips and so on).
*
* @param cursor
*/
void prepare(DBCursor cursor);
}

47
spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoTemplate.java

@ -33,6 +33,7 @@ import com.mongodb.BasicDBObject; @@ -33,6 +33,7 @@ import com.mongodb.BasicDBObject;
import com.mongodb.CommandResult;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.Mongo;
import com.mongodb.MongoException;
@ -142,9 +143,37 @@ public class MongoTemplate implements InitializingBean { @@ -142,9 +143,37 @@ public class MongoTemplate implements InitializingBean {
return action.doInDB(db);
} catch (MongoException e) {
throw MongoDbUtils.translateMongoExceptionIfPossible(e);
}
}
}
/**
* Executes the given {@link CollectionCallback} on the default collection.
*
* @param <T>
* @param callback
* @return
*/
public <T> T execute(CollectionCallback<T> callback) {
return execute(callback, defaultCollectionName);
}
/**
* Executes the given {@link CollectionCallback} on the collection of the given name.
*
* @param <T>
* @param callback
* @param collectionName
* @return
*/
public <T> T execute(CollectionCallback<T> callback, String collectionName) {
try {
return callback.doInCollection(getCollection(collectionName));
} catch (MongoException e) {
throw MongoDbUtils.translateMongoExceptionIfPossible(e);
}
}
public <T> T executeInSession(DBCallback<T> action) {
DB db = getDb();
db.requestStart();
@ -339,14 +368,26 @@ public class MongoTemplate implements InitializingBean { @@ -339,14 +368,26 @@ public class MongoTemplate implements InitializingBean {
return query(getDefaultCollectionName(), query, targetClass); //
}
public <T> List<T> query(DBObject query, Class<T> targetClass, CursorPreparer preparer) {
return query(getDefaultCollectionName(), query, targetClass, preparer); //
}
public <T> List<T> query(DBObject query, Class<T> targetClass, MongoReader<T> reader) {
return query(getDefaultCollectionName(), query, targetClass, reader);
}
public <T> List<T> query(String collectionName, DBObject query, Class<T> targetClass) {
return query(collectionName, query, targetClass, (CursorPreparer) null);
}
public <T> List<T> query(String collectionName, DBObject query, Class<T> targetClass, CursorPreparer preparer) {
DBCollection collection = getDb().getCollection(collectionName);
List<T> results = new ArrayList<T>();
for (DBObject dbo : collection.find(query)) {
DBCursor cursor = collection.find(query);
if (preparer != null) {
preparer.prepare(cursor);
}
for (DBObject dbo : cursor) {
Object obj = mongoConverter.read(targetClass,dbo);
//effectively acts as a query on the collection restricting it to elements of a specific type
if (targetClass.isInstance(obj)) {

153
spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoCursorUtils.java

@ -0,0 +1,153 @@ @@ -0,0 +1,153 @@
/*
* Copyright 2010 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.document.mongodb.repository;
import java.util.HashMap;
import java.util.Map;
import org.springframework.data.document.mongodb.CursorPreparer;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
import com.mongodb.BasicDBObject;
import com.mongodb.DBCursor;
/**
* Collection of utility methods to apply sorting and pagination to a
* {@link DBCursor}.
*
* @author Oliver Gierke
*/
class MongoCursorUtils {
/**
* Creates a {@link CursorPreparer} applying the given {@link Pageable} to
* the cursor.
*
* @param pageable
* @return
*/
public static CursorPreparer withPagination(Pageable pageable) {
return new PaginationCursorPreparer(pageable);
}
/**
* Creates a {@link CursorPreparer} to apply the given {@link Sort} to the
* cursor.
*
* @param sort
* @return
*/
public static CursorPreparer withSorting(Sort sort) {
return new SortingCursorPreparer(sort);
}
/**
* Applies the given {@link Pageable} to the given {@link DBCursor}.
*
* @author Oliver Gierke
*/
private static class PaginationCursorPreparer implements CursorPreparer {
private final Pageable pageable;
private final SortingCursorPreparer sortingPreparer;
/**
* Creates a new {@link PaginationCursorPreparer}.
*
* @param pageable
*/
public PaginationCursorPreparer(Pageable pageable) {
this.pageable = pageable;
this.sortingPreparer =
new SortingCursorPreparer(pageable.getSort());
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.data.document.mongodb.CursorPreparer#prepare(
* com.mongodb.DBCursor)
*/
public void prepare(DBCursor cursor) {
if (pageable == null) {
return;
}
int toSkip = pageable.getPageSize() * pageable.getPageNumber();
int first = pageable.getPageSize();
cursor.limit(first).skip(toSkip);
sortingPreparer.prepare(cursor);
}
}
/**
* Applies the given {@link Sort} to the given {@link DBCursor}.
*
* @author Oliver Gierke
*/
private static class SortingCursorPreparer implements CursorPreparer {
private final Sort sort;
/**
* Creates a new {@link SortingCursorPreparer}.
*
* @param sort
*/
public SortingCursorPreparer(Sort sort) {
this.sort = sort;
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.data.document.mongodb.CursorPreparer#prepare(
* com.mongodb.DBCursor)
*/
public void prepare(DBCursor cursor) {
if (sort == null) {
return;
}
Map<String, Integer> sorts = new HashMap<String, Integer>();
for (Order order : sort) {
sorts.put(order.getProperty(),
Direction.ASC.equals(order.getDirection()) ? 1 : -1);
}
cursor.sort(new BasicDBObject(sorts));
}
}
}

112
spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoQuery.java

@ -15,15 +15,22 @@ @@ -15,15 +15,22 @@
*/
package org.springframework.data.document.mongodb.repository;
import static org.springframework.data.document.mongodb.repository.MongoCursorUtils.*;
import java.util.List;
import org.springframework.data.document.mongodb.CollectionCallback;
import org.springframework.data.document.mongodb.MongoTemplate;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.SimpleParameterAccessor;
import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.util.Assert;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
@ -50,8 +57,6 @@ public class MongoQuery implements RepositoryQuery { @@ -50,8 +57,6 @@ public class MongoQuery implements RepositoryQuery {
Assert.notNull(template);
Assert.notNull(method);
Assert.isTrue(!method.isPageQuery(),
"Pagination queries not supported!");
this.method = method;
this.template = template;
@ -73,13 +78,106 @@ public class MongoQuery implements RepositoryQuery { @@ -73,13 +78,106 @@ public class MongoQuery implements RepositoryQuery {
MongoQueryCreator creator = new MongoQueryCreator(tree, accessor);
DBObject query = creator.createQuery();
List<?> result =
template.query(template.getDefaultCollectionName(), query,
method.getDomainClass());
if (method.isCollectionQuery()) {
return result;
return new CollectionExecution().execute(query);
} else if (method.isPageQuery()) {
return new PagedExecution(creator, accessor.getPageable())
.execute(query);
} else {
return new SingleEntityExecution().execute(query);
}
}
private abstract class Execution {
abstract Object execute(DBObject query);
protected List<?> readCollection(DBObject query) {
return template.query(template.getDefaultCollectionName(), query,
method.getDomainClass());
}
}
class CollectionExecution extends Execution {
@Override
public Object execute(DBObject query) {
return readCollection(query);
}
}
/**
* {@link Execution} for pagination queries.
*
* @author Oliver Gierke
*/
class PagedExecution extends Execution {
private final Pageable pageable;
private final MongoQueryCreator creator;
/**
* Creates a new {@link PagedExecution}.
*
* @param pageable
*/
public PagedExecution(MongoQueryCreator creator, Pageable pageable) {
Assert.notNull(creator);
Assert.notNull(pageable);
this.creator = creator;
this.pageable = pageable;
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.data.document.mongodb.repository.MongoQuery.Execution
* #execute(com.mongodb.DBObject)
*/
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
Object execute(DBObject query) {
int count = getCollectionCursor(creator.createQuery()).count();
List<?> result =
template.query(query, method.getDomainClass(),
withPagination(pageable));
return new PageImpl(result, pageable, count);
}
private DBCursor getCollectionCursor(final DBObject query) {
return template.execute(new CollectionCallback<DBCursor>() {
public DBCursor doInCollection(DBCollection collection) {
return collection.find(query);
}
});
}
}
/**
* {@link Execution} to return a single entity.
*
* @author Oliver Gierke
*/
class SingleEntityExecution extends Execution {
@Override
Object execute(DBObject query) {
List<?> result = readCollection(query);
return result.isEmpty() ? null : result.get(0);
}
}

3
spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoRepository.java

@ -17,6 +17,7 @@ package org.springframework.data.document.mongodb.repository; @@ -17,6 +17,7 @@ package org.springframework.data.document.mongodb.repository;
import java.io.Serializable;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.Repository;
@ -26,6 +27,6 @@ import org.springframework.data.repository.Repository; @@ -26,6 +27,6 @@ import org.springframework.data.repository.Repository;
* @author Oliver Gierke
*/
public interface MongoRepository<T, ID extends Serializable> extends
Repository<T, ID> {
PagingAndSortingRepository<T, Serializable> {
}

46
spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/SimpleMongoRepository.java

@ -15,15 +15,23 @@ @@ -15,15 +15,23 @@
*/
package org.springframework.data.document.mongodb.repository;
import static org.springframework.data.document.mongodb.repository.MongoCursorUtils.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import org.springframework.data.document.mongodb.MongoTemplate;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.support.IsNewAware;
import org.springframework.data.repository.support.RepositorySupport;
import org.springframework.util.Assert;
import com.mongodb.BasicDBObject;
import com.mongodb.QueryBuilder;
@ -33,7 +41,7 @@ import com.mongodb.QueryBuilder; @@ -33,7 +41,7 @@ import com.mongodb.QueryBuilder;
* @author Oliver Gierke
*/
public class SimpleMongoRepository<T, ID extends Serializable> extends
RepositorySupport<T, ID> {
RepositorySupport<T, ID> implements PagingAndSortingRepository<T, ID> {
private final MongoTemplate template;
private MongoEntityInformation entityInformation;
@ -132,7 +140,8 @@ public class SimpleMongoRepository<T, ID extends Serializable> extends @@ -132,7 +140,8 @@ public class SimpleMongoRepository<T, ID extends Serializable> extends
*/
public Long count() {
return Long.valueOf(findAll().size());
return template.getCollection(template.getDefaultCollectionName())
.count();
}
@ -176,6 +185,39 @@ public class SimpleMongoRepository<T, ID extends Serializable> extends @@ -176,6 +185,39 @@ public class SimpleMongoRepository<T, ID extends Serializable> extends
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.data.repository.PagingAndSortingRepository#findAll
* (org.springframework.data.domain.Pageable)
*/
public Page<T> findAll(final Pageable pageable) {
Long count = count();
List<T> list =
template.query(new BasicDBObject(), getDomainClass(),
withPagination(pageable));
return new PageImpl<T>(list, pageable, count);
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.data.repository.PagingAndSortingRepository#findAll
* (org.springframework.data.domain.Sort)
*/
public List<T> findAll(final Sort sort) {
return template.query(new BasicDBObject(), getDomainClass(),
withSorting(sort));
}
/*
* (non-Javadoc)
*

8
spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/Person.java

@ -32,7 +32,15 @@ public class Person { @@ -32,7 +32,15 @@ public class Person {
public Person() {
this(null, null);
}
public Person(String firstname, String lastname) {
this.id = ObjectId.get().toString();
this.firstname = firstname;
this.lastname = lastname;
}

27
spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/PersonRepository.java

@ -17,6 +17,9 @@ package org.springframework.data.document.mongodb.repository; @@ -17,6 +17,9 @@ package org.springframework.data.document.mongodb.repository;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
/**
* Sample repository managing {@link Person} entities.
@ -25,8 +28,32 @@ import java.util.List; @@ -25,8 +28,32 @@ import java.util.List;
*/
public interface PersonRepository extends MongoRepository<Person, Long> {
/**
* Returns all {@link Person}s with the given lastname.
*
* @param lastname
* @return
*/
List<Person> findByLastname(String lastname);
/**
* Returns all {@link Person}s with a firstname matching the given one
* (*-wildcard supported).
*
* @param firstname
* @return
*/
List<Person> findByFirstnameLike(String firstname);
/**
* Returns a page of {@link Person}s with a lastname mathing the given one
* (*-wildcards supported).
*
* @param lastname
* @param pageable
* @return
*/
Page<Person> findByLastnameLike(String lastname, Pageable pageable);
}

65
spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/PersonRepositoryIntegrationTests.java

@ -18,11 +18,16 @@ package org.springframework.data.document.mongodb.repository; @@ -18,11 +18,16 @@ package org.springframework.data.document.mongodb.repository;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@ -39,23 +44,63 @@ public class PersonRepositoryIntegrationTests { @@ -39,23 +44,63 @@ public class PersonRepositoryIntegrationTests {
@Autowired
PersonRepository repository;
Person dave, carter, boyd, stefan, leroi;
@Test
public void createAndExecuteFinderRoundtrip() throws Exception {
@Before
public void setUp() {
repository.deleteAll();
Person person = new Person();
person.setFirstname("Oliver");
person.setLastname("Gierke");
person = repository.save(person);
dave = new Person("Dave", "Matthews");
carter = new Person("Carter", "Beauford");
boyd = new Person("Boyd", "Tinsley");
stefan = new Person("Stefan", "Lessard");
leroi = new Person("Leroi", "Moore");
repository.save(Arrays.asList(dave, carter, boyd, stefan, leroi));
}
List<Person> result = repository.findByLastname("Gierke");
@Test
public void findsPersonsByLastname() throws Exception {
List<Person> result = repository.findByLastname("Beauford");
assertThat(result.size(), is(1));
assertThat(result, hasItem(person));
assertThat(result, hasItem(carter));
}
@Test
public void findsPersonsByFirstnameLike() throws Exception {
result = repository.findByFirstnameLike("Oli*");
List<Person> result = repository.findByFirstnameLike("Bo*");
assertThat(result.size(), is(1));
assertThat(result, hasItem(person));
assertThat(result, hasItem(boyd));
}
@Test
public void findsPagedPersons() throws Exception {
Page<Person> result =
repository.findAll(new PageRequest(1, 2, Direction.ASC,
"lastname"));
assertThat(result.isFirstPage(), is(false));
assertThat(result.isLastPage(), is(false));
assertThat(result, hasItems(dave, leroi));
}
@Test
public void executesPagedFinderCorrectly() throws Exception {
Page<Person> page =
repository.findByLastnameLike("*a*", new PageRequest(0, 2,
Direction.ASC, "lastname"));
assertThat(page.isFirstPage(), is(true));
assertThat(page.isLastPage(), is(false));
assertThat(page.getNumberOfElements(), is(2));
assertThat(page, hasItems(carter, stefan));
}
}

Loading…
Cancel
Save