DATAJPA-142 - Allow using a NamedQuery for count queries in pagination.

We will now prefer a declared NamedQuery for count queries instead of deriving it from the actual NamedQuery under the following conditions:

- by default a query named ${namedQueryName}.count exists
- the name of the NamedQuery to be used is defined in @Query(countQueryName = "…")

Note that a potential reconfiguration of the NamedQuery name will be taken into account for the according count query name to assure symmetry. Examples

Page<User> findByLastname(String lastname, Pageable pageable)
NamedQuery name: User.findByLastname
Count NamedQuery name: User.findByLastname.count

@Query(name = "Foo.bar")
Page<User> findByLastname(String lastname, Pageable pageable)
NamedQuery name: Foo.bar
Count NamedQuery name: Foo.bar.count

@Query(countName = "Foo.bar.count")
Page<User> findByLastname(String lastname, Pageable pageable)
NamedQuery name: User.findByLastname
Count NamedQuery name: Foo.bar.count

@Query(name = "Foo.bar", countName = "something")
Page<User> findByLastname(String lastname, Pageable pageable)
NamedQuery name: Foo.bar
Count NamedQuery name: something
This commit is contained in:
Oliver Gierke
2012-01-11 15:12:05 +01:00
parent 9224a4b14f
commit 1a14e92e69
5 changed files with 99 additions and 12 deletions

View File

@@ -21,6 +21,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.persistence.NamedQuery;
/**
* Annotation to declare finder queries directly on repository methods.
*
@@ -48,8 +50,17 @@ public @interface Query {
boolean nativeQuery() default false;
/**
* The named query to be used. If not defined, a NamedQuery with name of {@code $ domainClass}.${finderMethodName}}
* The named query to be used. If not defined, a NamedQuery with name of {@code $ domainClass}.${queryMethodName}}
* will be used.
*/
String name() default "";
/**
* Returns the name of the {@link NamedQuery} to be used to execute count queries when pagination is used. Will
* default to the named query name configured suffixed by {@code .count}.
*
* @see #name()
* @return
*/
String countName() default "";
}

View File

@@ -182,6 +182,17 @@ public class JpaQueryMethod extends QueryMethod {
return StringUtils.hasText(annotatedName) ? annotatedName : super.getNamedQueryName();
}
/**
* Returns the name of the {@link NamedQuery} that shall be used for count queries.
*
* @return
*/
String getNamedCountQueryName() {
String annotatedName = getAnnotationValue("countName", String.class);
return StringUtils.hasText(annotatedName) ? annotatedName : getNamedQueryName() + ".count";
}
/**
* Returns whether we should clear automatically for modifying queries.
*

View File

@@ -41,6 +41,7 @@ final class NamedQuery extends AbstractJpaQuery {
private static final Logger LOG = LoggerFactory.getLogger(NamedQuery.class);
private final String queryName;
private final String countQueryName;
private final QueryExtractor extractor;
/**
@@ -51,6 +52,7 @@ final class NamedQuery extends AbstractJpaQuery {
super(method, em);
this.queryName = method.getNamedQueryName();
this.countQueryName = method.getNamedCountQueryName();
this.extractor = method.getQueryExtractor();
Parameters parameters = method.getParameters();
@@ -65,19 +67,32 @@ final class NamedQuery extends AbstractJpaQuery {
+ "via this Pageable will not be applied!", method);
}
boolean weNeedToCreateCountQuery = method.getParameters().hasPageableParameter();
boolean weNeedToCreateCountQuery = !hasNamedQuery(em, countQueryName)
&& method.getParameters().hasPageableParameter();
boolean cantExtractQuery = !this.extractor.canExtractQuery();
if (weNeedToCreateCountQuery && cantExtractQuery) {
throw QueryCreationException.create(method, CANNOT_EXTRACT_QUERY);
}
Query query = em.createNamedQuery(queryName);
// Let's see if the referenced named query exists
em.createNamedQuery(queryName);
}
// Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=322579
// until it gets fixed
if (null != query) {
query.getHints();
/**
* Returns whether the named query with the given name exists.
*
* @param em
* @return
*/
private static final boolean hasNamedQuery(EntityManager em, String queryName) {
try {
em.createNamedQuery(queryName);
return true;
} catch (IllegalArgumentException e) {
LOG.debug("Did not find named query {}", queryName);
return false;
}
}
@@ -118,10 +133,17 @@ final class NamedQuery extends AbstractJpaQuery {
@Override
protected TypedQuery<Long> doCreateCountQuery(Object[] values) {
Query query = createQuery(values);
String queryString = extractor.extractQueryString(query);
EntityManager em = getEntityManager();
TypedQuery<Long> countQuery = null;
return createBinder(values).bind(
getEntityManager().createQuery(QueryUtils.createCountQueryFor(queryString), Long.class));
if (hasNamedQuery(em, countQueryName)) {
countQuery = em.createNamedQuery(countQueryName, Long.class);
} else {
Query query = createQuery(values);
String queryString = extractor.extractQueryString(query);
countQuery = em.createQuery(QueryUtils.createCountQueryFor(queryString), Long.class);
}
return createBinder(values).bind(countQuery);
}
}

View File

@@ -17,6 +17,7 @@ package org.springframework.data.jpa.repository.query;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.lang.reflect.Method;
import java.util.List;
@@ -230,6 +231,29 @@ public class JpaQueryMethodUnitTests {
assertEquals(LockModeType.PESSIMISTIC_WRITE, lockMode);
}
/**
* @see DATAJPA-142
*/
@Test
@SuppressWarnings({ "unchecked", "rawtypes" })
public void returnsDefaultCountQueryName() {
when(metadata.getReturnedDomainClass(repositoryMethod)).thenReturn((Class) User.class);
JpaQueryMethod method = new JpaQueryMethod(repositoryMethod, metadata, extractor);
assertThat(method.getNamedCountQueryName(), is("User.findByLastname.count"));
}
/**
* @see DATAJPA-142
*/
@Test
public void returnsDefaultCountQueryNameBasedOnConfiguredNamedQueryName() {
JpaQueryMethod method = new JpaQueryMethod(namedQuery, metadata, extractor);
assertThat(method.getNamedCountQueryName(), is("Foo.bar.count"));
}
/**
* Interface to define invalid repository methods for testing.
*

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.data.jpa.repository.query;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.lang.reflect.Method;
@@ -61,11 +62,29 @@ public class NamedQueryUnitTests {
public void rejectsPersistenceProviderIfIncapableOfExtractingQueriesAndPagebleBeingUsed() {
when(extractor.canExtractQuery()).thenReturn(false);
JpaQueryMethod queryMethod = new JpaQueryMethod(method, metadata, extractor);
when(em.createNamedQuery(queryMethod.getNamedCountQueryName())).thenThrow(new IllegalArgumentException());
NamedQuery.lookupFrom(queryMethod, em);
}
/**
* @see DATAJPA-142
*/
@Test
public void doesNotRejectPersistenceProviderIfNamedCountQueryIsAvailable() {
when(extractor.canExtractQuery()).thenReturn(false);
JpaQueryMethod queryMethod = new JpaQueryMethod(method, metadata, extractor);
when(em.createNamedQuery(queryMethod.getNamedCountQueryName())).thenReturn(null);
NamedQuery query = (NamedQuery) NamedQuery.lookupFrom(queryMethod, em);
query.doCreateCountQuery(new Object[1]);
verify(em, times(1)).createNamedQuery(queryMethod.getNamedCountQueryName(), Long.class);
verify(em, never()).createQuery(any(String.class), eq(Long.class));
}
interface SampleRepository {
Page<String> foo(Pageable pageable);