DATAMONGO-636 - Added support for countBy in derived queries.

We now change the query execution to a count execution in case a derived query has the PartTree.isCountProjection() set to true or a query defined in @Query has the newly introduced count() attribute set to true.

As the native Mongo count() returns a long we use a default ConversionService to potentially massage the query result into other numerical types.
This commit is contained in:
Oliver Gierke
2013-03-26 17:11:09 +01:00
parent 509be3d681
commit ab18bb5b96
7 changed files with 123 additions and 19 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011 the original author or authors.
* Copyright 2011-2013 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.
@@ -15,7 +15,11 @@
*/
package org.springframework.data.mongodb.repository;
import java.lang.annotation.*;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to declare finder queries directly on repository methods. Both attributes allow using a placeholder
@@ -43,4 +47,12 @@ public @interface Query {
* @return
*/
String fields() default "";
/**
* Returns whether the query defined should be executed as count projection.
*
* @since 1.3
* @return
*/
boolean count() default false;
}

View File

@@ -17,6 +17,8 @@ package org.springframework.data.mongodb.repository.query;
import java.util.List;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.core.MongoOperations;
@@ -39,6 +41,8 @@ import org.springframework.util.Assert;
*/
public abstract class AbstractMongoQuery implements RepositoryQuery {
private static final ConversionService CONVERSION_SERVICE = new DefaultConversionService();
private final MongoQueryMethod method;
private final MongoOperations operations;
@@ -86,18 +90,22 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
return new CollectionExecution(accessor.getPageable()).execute(query);
} else if (method.isPageQuery()) {
return new PagedExecution(accessor.getPageable()).execute(query);
} else {
return new SingleEntityExecution().execute(query);
}
}
/**
* Creates a {@link Query} instance using the given {@link ParameterAccessor}
*
* @param accessor must not be {@literal null}.
* @return
*/
protected abstract Query createQuery(ConvertingParameterAccessor accessor);
Object result = new SingleEntityExecution(isCountQuery()).execute(query);
if (result == null) {
return result;
}
Class<?> expectedReturnType = method.getReturnType().getType();
if (expectedReturnType.isAssignableFrom(result.getClass())) {
return result;
}
return CONVERSION_SERVICE.convert(result, expectedReturnType);
}
/**
* Creates a {@link Query} instance using the given {@link ConvertingParameterAccessor}. Will delegate to
@@ -111,6 +119,21 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
return createQuery(accessor);
}
/**
* Creates a {@link Query} instance using the given {@link ParameterAccessor}
*
* @param accessor must not be {@literal null}.
* @return
*/
protected abstract Query createQuery(ConvertingParameterAccessor accessor);
/**
* Returns whether the query should get a count projection applied.
*
* @return
*/
protected abstract boolean isCountQuery();
private abstract class Execution {
abstract Object execute(Query query);
@@ -191,6 +214,12 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
*/
class SingleEntityExecution extends Execution {
private final boolean countProjection;
private SingleEntityExecution(boolean countProjection) {
this.countProjection = countProjection;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.AbstractMongoQuery.Execution#execute(org.springframework.data.mongodb.core.core.query.Query)
@@ -199,7 +228,8 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
Object execute(Query query) {
MongoEntityMetadata<?> metadata = method.getEntityInformation();
return operations.findOne(query, metadata.getJavaType());
return countProjection ? operations.count(query, metadata.getJavaType()) : operations.findOne(query,
metadata.getJavaType());
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2012 the original author or authors.
* Copyright 2011-2013 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.
@@ -171,7 +171,7 @@ public class MongoQueryMethod extends QueryMethod {
*
* @return
*/
private Query getQueryAnnotation() {
Query getQueryAnnotation() {
return method.getAnnotation(Query.class);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2013 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.
@@ -77,4 +77,13 @@ public class PartTreeMongoQuery extends AbstractMongoQuery {
protected Query createCountQuery(ConvertingParameterAccessor accessor) {
return new MongoQueryCreator(tree, accessor, context, false).createQuery();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isCountQuery()
*/
@Override
protected boolean isCountQuery() {
return tree.isCountProjection();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2012 the original author or authors.
* Copyright 2011-2013 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.
@@ -38,17 +38,21 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
private final String query;
private final String fieldSpec;
private final boolean isCountQuery;
/**
* Creates a new {@link StringBasedMongoQuery}.
*
* @param method
* @param template
* @param method must not be {@literal null}.
* @param template must not be {@literal null}.
*/
public StringBasedMongoQuery(String query, MongoQueryMethod method, MongoOperations mongoOperations) {
super(method, mongoOperations);
this.query = query;
this.fieldSpec = method.getFieldSpecification();
this.isCountQuery = method.hasAnnotatedQuery() ? method.getQueryAnnotation().count() : false;
}
public StringBasedMongoQuery(MongoQueryMethod method, MongoOperations mongoOperations) {
@@ -82,6 +86,15 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
return query;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isCountQuery()
*/
@Override
protected boolean isCountQuery() {
return isCountQuery;
}
private String replacePlaceholders(String input, ConvertingParameterAccessor accessor) {
Matcher matcher = PLACEHOLDER.matcher(input);

View File

@@ -546,4 +546,28 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
assertThat(result, hasSize(1));
assertThat(result, hasItem(dave));
}
/**
* @see DATAMONGO-636
*/
@Test
public void executesDerivedCountProjection() {
assertThat(repository.countByLastname("Matthews"), is(2L));
}
/**
* @see DATAMONGO-636
*/
@Test
public void executesDerivedCountProjectionToInt() {
assertThat(repository.countByFirstname("Oliver August"), is(1));
}
/**
* @see DATAMONGO-636
*/
@Test
public void executesAnnotatedCountProjection() {
assertThat(repository.someCountQuery("Matthews"), is(2L));
}
}

View File

@@ -198,4 +198,20 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
* @return
*/
List<Person> findByCredentials(Credentials credentials);
/**
* @see DATAMONGO-636
*/
long countByLastname(String lastname);
/**
* @see DATAMONGO-636
*/
int countByFirstname(String firstname);
/**
* @see DATAMONGO-636
*/
@Query(value = "{ 'lastname' : ?0 }", count = true)
long someCountQuery(String lastname);
}