From ab18bb5b961e19bffab6ffd739a7f3fea5ccabcf Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Tue, 26 Mar 2013 17:11:09 +0100 Subject: [PATCH] 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. --- .../data/mongodb/repository/Query.java | 16 +++++- .../repository/query/AbstractMongoQuery.java | 50 +++++++++++++++---- .../repository/query/MongoQueryMethod.java | 4 +- .../repository/query/PartTreeMongoQuery.java | 11 +++- .../query/StringBasedMongoQuery.java | 19 +++++-- ...tractPersonRepositoryIntegrationTests.java | 24 +++++++++ .../mongodb/repository/PersonRepository.java | 16 ++++++ 7 files changed, 122 insertions(+), 18 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Query.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Query.java index 3c7fe406c..6c02accfc 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Query.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Query.java @@ -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; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java index 278fd89b3..9f90fa3d4 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java @@ -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,9 +90,33 @@ 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); } + + 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 + * {@link #createQuery(ConvertingParameterAccessor)} by default but allows customization of the count query to be + * triggered. + * + * @param accessor must not be {@literal null}. + * @return + */ + protected Query createCountQuery(ConvertingParameterAccessor accessor) { + return createQuery(accessor); } /** @@ -100,16 +128,11 @@ public abstract class AbstractMongoQuery implements RepositoryQuery { protected abstract Query createQuery(ConvertingParameterAccessor accessor); /** - * Creates a {@link Query} instance using the given {@link ConvertingParameterAccessor}. Will delegate to - * {@link #createQuery(ConvertingParameterAccessor)} by default but allows customization of the count query to be - * triggered. + * Returns whether the query should get a count projection applied. * - * @param accessor must not be {@literal null}. * @return */ - protected Query createCountQuery(ConvertingParameterAccessor accessor) { - return createQuery(accessor); - } + protected abstract boolean isCountQuery(); private abstract class Execution { @@ -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()); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java index a5bbfd203..034261078 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java @@ -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); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java index efd5adc2d..eb34feb3d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java @@ -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(); + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java index 71ae4ff81..20b8d0c0f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java @@ -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); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java index 74f5460db..2f4a50b0d 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java @@ -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)); + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java index 7b7424055..68378692e 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java @@ -198,4 +198,20 @@ public interface PersonRepository extends MongoRepository, Query * @return */ List 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); }