From 0bf2cb391efed388e8e8981f586537a7304df5a1 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Tue, 26 Mar 2013 15:21:26 +0100 Subject: [PATCH] DATAJPA-231 - Added support for derived countBy queries. PartTreeJpaQuery evaluates the newly introduced PartTree.isCountProjection() to rather trigger a derived count query instead of the actual query. Extended Query execution to potentially massage the returned value into the return type of the query method to allow long and int as return types for count queries. --- .../query/JpaCountQueryCreator.java | 13 ++------- .../repository/query/JpaQueryExecution.java | 27 ++++++++++++++++--- .../repository/query/PartTreeJpaQuery.java | 15 +++++------ .../jpa/repository/UserRepositoryTests.java | 22 +++++++++++++++ .../query/JpaQueryExecutionUnitTests.java | 3 ++- .../jpa/repository/sample/UserRepository.java | 10 +++++++ 6 files changed, 65 insertions(+), 25 deletions(-) diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreator.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreator.java index 4ec54023d..baf3a2a12 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreator.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2011 the original author or authors. + * Copyright 2008-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. @@ -40,25 +40,16 @@ public class JpaCountQueryCreator extends JpaQueryCreator { */ public JpaCountQueryCreator(PartTree tree, Class domainClass, CriteriaBuilder builder, ParameterMetadataProvider provider) { - super(tree, domainClass, builder, provider); } /* * (non-Javadoc) - * - * @see - * org.springframework.data.jpa.repository.query.JpaQueryCreator#complete - * (javax.persistence.criteria.Predicate, - * org.springframework.data.domain.Sort, - * javax.persistence.criteria.CriteriaQuery, - * javax.persistence.criteria.CriteriaBuilder, - * javax.persistence.criteria.Root) + * @see org.springframework.data.jpa.repository.query.JpaQueryCreator#complete(javax.persistence.criteria.Predicate, org.springframework.data.domain.Sort, javax.persistence.criteria.CriteriaQuery, javax.persistence.criteria.CriteriaBuilder, javax.persistence.criteria.Root) */ @Override protected CriteriaQuery complete(Predicate predicate, Sort sort, CriteriaQuery query, CriteriaBuilder builder, Root root) { - return query.select(builder.count(root)).where(predicate); } } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java index 9d892d9e6..ca13f298e 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2011 the original author or authors. + * Copyright 2008-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. @@ -23,6 +23,8 @@ import javax.persistence.NoResultException; import javax.persistence.Query; import javax.persistence.TypedQuery; +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.repository.query.ParameterAccessor; @@ -39,11 +41,13 @@ import org.springframework.util.Assert; */ public abstract class JpaQueryExecution { + private static final ConversionService conversionService = new DefaultConversionService(); + /** * Executes the given {@link AbstractStringBasedJpaQuery} with the given {@link ParameterBinder}. * - * @param query - * @param binder + * @param query must not be {@literal null}. + * @param binder must not be {@literal null}. * @return */ public Object execute(AbstractJpaQuery query, Object[] values) { @@ -51,11 +55,26 @@ public abstract class JpaQueryExecution { Assert.notNull(query); Assert.notNull(values); + Object result; + try { - return doExecute(query, values); + result = doExecute(query, values); } catch (NoResultException e) { return null; } + + if (result == null) { + return result; + } + + JpaQueryMethod queryMethod = query.getQueryMethod(); + Class requiredType = queryMethod.getReturnType(); + + if (void.class.equals(requiredType) || requiredType.isAssignableFrom(result.getClass())) { + return result; + } + + return conversionService.convert(result, requiredType); } /** diff --git a/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java index 868c06bfe..55e109748 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2011 the original author or authors. + * Copyright 2008-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. @@ -46,8 +46,8 @@ public class PartTreeJpaQuery extends AbstractJpaQuery { /** * Creates a new {@link PartTreeJpaQuery}. * - * @param method - * @param em + * @param method must not be {@literal null}. + * @param em must not be {@literal null}. */ public PartTreeJpaQuery(JpaQueryMethod method, EntityManager em) { @@ -57,8 +57,8 @@ public class PartTreeJpaQuery extends AbstractJpaQuery { this.tree = new PartTree(method.getName(), domainClass); this.parameters = method.getParameters(); - this.query = new QueryPreparer(parameters.potentiallySortsDynamically()); this.countQuery = new CountQueryPreparer(parameters.potentiallySortsDynamically()); + this.query = tree.isCountProjection() ? countQuery : new QueryPreparer(parameters.potentiallySortsDynamically()); } /* @@ -163,15 +163,12 @@ public class PartTreeJpaQuery extends AbstractJpaQuery { private class CountQueryPreparer extends QueryPreparer { public CountQueryPreparer(boolean recreateQueries) { - super(recreateQueries); } /* * (non-Javadoc) - * - * @see org.springframework.data.jpa.repository.query.PartTreeJpaQuery. - * QueryPreparer#createCreator() + * @see org.springframework.data.jpa.repository.query.PartTreeJpaQuery.QueryPreparer#createCreator(org.springframework.data.repository.query.ParametersParameterAccessor) */ @Override protected JpaQueryCreator createCreator(ParametersParameterAccessor accessor) { @@ -191,8 +188,8 @@ public class PartTreeJpaQuery extends AbstractJpaQuery { * @see org.springframework.data.jpa.repository.query.PartTreeJpaQuery.QueryPreparer#invokeBinding(org.springframework.data.jpa.repository.query.ParameterBinder, * javax.persistence.TypedQuery) */ + @Override protected Query invokeBinding(ParameterBinder binder, javax.persistence.TypedQuery query) { - return binder.bind(query); } } diff --git a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java index 251798086..c2c8cdd39 100644 --- a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java @@ -1027,6 +1027,28 @@ public class UserRepositoryTests { assertThat(result, hasItem(thirdUser)); } + /** + * @see DATAJPA-231 + */ + @Test + public void executesDerivedCountQueryToLong() { + + flushTestUsers(); + + assertThat(repository.countByLastname("Matthews"), is(1L)); + } + + /** + * @see DATAJPA-231 + */ + @Test + public void executesDerivedCountQueryToInt() { + + flushTestUsers(); + + assertThat(repository.countUsersByFirstname("Dave"), is(1)); + } + private Page executeSpecWithSort(Sort sort) { flushTestUsers(); diff --git a/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryExecutionUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryExecutionUnitTests.java index e8f24ef97..c159da311 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryExecutionUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryExecutionUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2011 the original author or authors. + * Copyright 2008-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. @@ -88,6 +88,7 @@ public class JpaQueryExecutionUnitTests { when(query.executeUpdate()).thenReturn(0); when(method.getReturnType()).thenReturn((Class) void.class); when(jpaQuery.createQuery(Mockito.any(Object[].class))).thenReturn(query); + when(jpaQuery.getQueryMethod()).thenReturn(method); ModifyingExecution execution = new ModifyingExecution(method, em); execution.execute(jpaQuery, new Object[] {}); diff --git a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java index c9d699d89..acff3a0ef 100644 --- a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java +++ b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java @@ -254,4 +254,14 @@ public interface UserRepository extends JpaRepository, JpaSpecifi @Query(value = "SELECT 1 FROM User", nativeQuery = true) List findOnesByNativeQuery(); + + /** + * @see DATAJPA-231 + */ + long countByLastname(String lastname); + + /** + * @see DATAJPA-231 + */ + int countUsersByFirstname(String firstname); }