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:
@@ -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 "";
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user