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.
This commit is contained in:
committed by
Oliver Gierke
parent
d4acfeb0fa
commit
122d9c5ea2
@@ -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}.
|
||||
*/
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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, "");
|
||||
}
|
||||
|
||||
@@ -1393,6 +1393,34 @@ public class UserRepositoryTests {
|
||||
assertThat(result, is(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-456
|
||||
*/
|
||||
@Test
|
||||
public void findPaginatedExplicitQueryWithCountQueryProjection() {
|
||||
|
||||
firstUser.setFirstname(null);
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
Page<User> result = repository.findAllByFirstnameLike("", new PageRequest(0, 10));
|
||||
|
||||
assertThat(result.getContent().size(), is(3));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-456
|
||||
*/
|
||||
@Test
|
||||
public void findPaginatedNamedQueryWithCountQueryProjection() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
Page<User> result = repository.findByNamedQueryAndCountProjection("Gierke", new PageRequest(0, 10));
|
||||
|
||||
assertThat(result.getContent().size(), is(1));
|
||||
}
|
||||
|
||||
private Page<User> executeSpecWithSort(Sort sort) {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -368,4 +368,16 @@ public interface UserRepository extends JpaRepository<User, Integer>, 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<User> findAllByFirstnameLike(String firstname, Pageable page);
|
||||
|
||||
/**
|
||||
* @see DATAJPA-456
|
||||
*/
|
||||
@Query(name = "User.findBySpringDataNamedQuery", countProjection = "u.firstname")
|
||||
Page<User> findByNamedQueryAndCountProjection(String firstname, Pageable page);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user