From 122d9c5ea2c87c6e59f193977260d878cd0dda81 Mon Sep 17 00:00:00 2001 From: Thomas Darimont Date: Tue, 29 Apr 2014 01:23:01 +0200 Subject: [PATCH] DATAJPA-456 - Add support for specifying only the projection part of a custom count query. We now support to specify just the projection part for the count query generation via the "countProjection" property of the @Query annotation. This avoids having to repetitively specify parts of the original query. Original pull request: #84. --- .../data/jpa/repository/Query.java | 12 ++++++- .../query/AbstractStringBasedJpaQuery.java | 4 +-- .../jpa/repository/query/JpaQueryMethod.java | 13 +++++++ .../data/jpa/repository/query/NamedQuery.java | 4 ++- .../data/jpa/repository/query/QueryUtils.java | 35 ++++++++++++++----- .../jpa/repository/UserRepositoryTests.java | 28 +++++++++++++++ .../repository/query/QueryUtilsUnitTests.java | 11 +++++- .../jpa/repository/sample/UserRepository.java | 12 +++++++ 8 files changed, 106 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/springframework/data/jpa/repository/Query.java b/src/main/java/org/springframework/data/jpa/repository/Query.java index 849726006..ecc0d7772 100644 --- a/src/main/java/org/springframework/data/jpa/repository/Query.java +++ b/src/main/java/org/springframework/data/jpa/repository/Query.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2012 the original author or authors. + * Copyright 2008-2014 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. @@ -27,6 +27,7 @@ import org.springframework.data.annotation.QueryAnnotation; * Annotation to declare finder queries directly on repository methods. * * @author Oliver Gierke + * @author Thomas Darimont */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @@ -45,6 +46,15 @@ public @interface Query { */ String countQuery() default ""; + /** + * Defines the projection part of the count query that is generated for pagination. If neither {@link #countQuery()} + * not {@link #countProjection()} is configured we will derive the count query from the method name. + * + * @return + * @since 1.6 + */ + String countProjection() default ""; + /** * Configures whether the given query is a native one. Defaults to {@literal false}. */ diff --git a/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java index 18f00361b..4df319c22 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2013 the original author or authors. + * Copyright 2008-2014 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. @@ -50,7 +50,7 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery { this.query = new ExpressionBasedStringQuery(queryString, method.getEntityInformation()); this.countQuery = new StringQuery(method.getCountQuery() != null ? method.getCountQuery() - : QueryUtils.createCountQueryFor(this.query.getQueryString())); + : QueryUtils.createCountQueryFor(this.query.getQueryString(), method.getCountQueryProjection())); } /* diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java index 24b489352..25bf77fe9 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java @@ -232,6 +232,19 @@ public class JpaQueryMethod extends QueryMethod { return StringUtils.hasText(countQuery) ? countQuery : null; } + /** + * Returns the count query projection string declared in a {@link Query} annotation or {@literal null} if neither the + * annotation found nor the attribute was specified. + * + * @return + * @since 1.6 + */ + String getCountQueryProjection() { + + String countProjection = getAnnotationValue("countProjection", String.class); + return StringUtils.hasText(countProjection) ? countProjection : null; + } + /** * Returns whether the backing query is a native one. * diff --git a/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java index 08e270ff3..0829e4b05 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java @@ -41,6 +41,7 @@ final class NamedQuery extends AbstractJpaQuery { private final String queryName; private final String countQueryName; + private final String countProjection; private final QueryExtractor extractor; /** @@ -53,6 +54,7 @@ final class NamedQuery extends AbstractJpaQuery { this.queryName = method.getNamedQueryName(); this.countQueryName = method.getNamedCountQueryName(); this.extractor = method.getQueryExtractor(); + this.countProjection = method.getCountQueryProjection(); Parameters parameters = method.getParameters(); @@ -142,7 +144,7 @@ final class NamedQuery extends AbstractJpaQuery { } else { Query query = createQuery(values); String queryString = extractor.extractQueryString(query); - countQuery = em.createQuery(QueryUtils.createCountQueryFor(queryString), Long.class); + countQuery = em.createQuery(QueryUtils.createCountQueryFor(queryString, countProjection), Long.class); } return createBinder(values).bind(countQuery); diff --git a/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java b/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java index 265a83a7b..3ed81b3c1 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java @@ -332,22 +332,41 @@ public abstract class QueryUtils { } /** - * Creates a count projected query from the given orginal query. + * Creates a count projected query from the given original query. * - * @param originalQuery must not be {@literal null} or empty + * @param originalQuery must not be {@literal null} or empty. * @return */ public static String createCountQueryFor(String originalQuery) { + return createCountQueryFor(originalQuery, null); + } - Assert.hasText(originalQuery); + /** + * Creates a count projected query from the given original query. + * + * @param originalQuery must not be {@literal null}. + * @param countProjection may be {@literal null}. + * @return + * @since 1.6 + */ + public static String createCountQueryFor(String originalQuery, String countProjection) { + + Assert.hasText(originalQuery, "OriginalQuery must not be null or empty!"); Matcher matcher = COUNT_MATCH.matcher(originalQuery); - String variable = matcher.matches() ? matcher.group(4) : null; - boolean useVariable = StringUtils.hasText(variable) && !variable.startsWith("new") - && !variable.startsWith("count(") && !variable.contains(","); + String countQuery = null; - String countQuery = matcher.replaceFirst(String.format(COUNT_REPLACEMENT_TEMPLATE, useVariable ? SIMPLE_COUNT_VALUE - : COMPLEX_COUNT_VALUE)); + if (countProjection == null) { + + String variable = matcher.matches() ? matcher.group(4) : null; + boolean useVariable = variable != null && StringUtils.hasText(variable) && !variable.startsWith("new") + && !variable.startsWith("count(") && !variable.contains(","); + + String replacement = useVariable ? SIMPLE_COUNT_VALUE : COMPLEX_COUNT_VALUE; + countQuery = matcher.replaceFirst(String.format(COUNT_REPLACEMENT_TEMPLATE, replacement)); + } else { + countQuery = matcher.replaceFirst(String.format(COUNT_REPLACEMENT_TEMPLATE, countProjection)); + } return countQuery.replaceFirst(ORDER_BY_PART, ""); } 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 d4b84de3e..f17191753 100644 --- a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java @@ -1393,6 +1393,34 @@ public class UserRepositoryTests { assertThat(result, is(data)); } + /** + * @see DATAJPA-456 + */ + @Test + public void findPaginatedExplicitQueryWithCountQueryProjection() { + + firstUser.setFirstname(null); + + flushTestUsers(); + + Page result = repository.findAllByFirstnameLike("", new PageRequest(0, 10)); + + assertThat(result.getContent().size(), is(3)); + } + + /** + * @see DATAJPA-456 + */ + @Test + public void findPaginatedNamedQueryWithCountQueryProjection() { + + flushTestUsers(); + + Page result = repository.findByNamedQueryAndCountProjection("Gierke", new PageRequest(0, 10)); + + assertThat(result.getContent().size(), is(1)); + } + private Page executeSpecWithSort(Sort sort) { flushTestUsers(); diff --git a/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsUnitTests.java index 977642210..03a96e23f 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2013 the original author or authors. + * Copyright 2008-2014 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. @@ -272,6 +272,15 @@ public class QueryUtilsUnitTests { assertCountQuery("select p.lastname,p.firstname from Person p", "select count(p) from Person p"); } + /** + * @see DATAJPA-456 + */ + @Test + public void createCountQueryFromTheGivenCountProjection() { + assertThat(createCountQueryFor("select p.lastname,p.firstname from Person p", "p.lastname"), + is("select count(p.lastname) from Person p")); + } + private void assertCountQuery(String originalQuery, String countQuery) { assertThat(createCountQueryFor(originalQuery), is(countQuery)); } 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 0b04c3321..2bf94b829 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 @@ -368,4 +368,16 @@ public interface UserRepository extends JpaRepository, JpaSpecifi */ @Procedure Integer plus1(@Param("arg") Integer arg); + + /** + * @see DATAJPA-456 + */ + @Query(value = "select u from User u where u.firstname like ?1%", countProjection = "u.firstname") + Page findAllByFirstnameLike(String firstname, Pageable page); + + /** + * @see DATAJPA-456 + */ + @Query(name = "User.findBySpringDataNamedQuery", countProjection = "u.firstname") + Page findByNamedQueryAndCountProjection(String firstname, Pageable page); }