DATAJPA-86 - Pagination over queries using group-by works correctly.

Added handling of count methods for queries using group by. In case the count query returns multiple results we use the number of results instead of failing. If the result contains one result we use this one.

Moved hint handling to AbstractJpaQuery.
This commit is contained in:
Oliver Gierke
2011-09-04 16:56:50 +02:00
parent 170d31707c
commit 04349d25dc
10 changed files with 89 additions and 85 deletions

View File

@@ -17,12 +17,13 @@ package org.springframework.data.jpa.repository.query;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.QueryHint;
import javax.persistence.TypedQuery;
import org.springframework.data.jpa.repository.query.JpaQueryExecution.CollectionExecution;
import org.springframework.data.jpa.repository.query.JpaQueryExecution.ModifyingExecution;
import org.springframework.data.jpa.repository.query.JpaQueryExecution.PagedExecution;
import org.springframework.data.jpa.repository.query.JpaQueryExecution.SingleEntityExecution;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.util.Assert;
@@ -58,7 +59,7 @@ public abstract class AbstractJpaQuery implements RepositoryQuery {
* org.springframework.data.repository.query.RepositoryQuery#getQueryMethod
* ()
*/
public QueryMethod getQueryMethod() {
public JpaQueryMethod getQueryMethod() {
return method;
}
@@ -106,12 +107,46 @@ public abstract class AbstractJpaQuery implements RepositoryQuery {
}
}
protected ParameterBinder createBinder(Object[] values) {
/**
* Applies the declared query hints to the given query.
*
* @param query
* @return
*/
private <T extends Query> T applyHints(T query, JpaQueryMethod method) {
for (QueryHint hint : method.getHints()) {
query.setHint(hint.name(), hint.value());
}
return query;
}
protected ParameterBinder createBinder(Object[] values) {
return new ParameterBinder(getQueryMethod().getParameters(), values);
}
protected abstract Query createQuery(Object[] values);
protected Query createQuery(Object[] values) {
return applyHints(doCreateQuery(values), method);
}
protected abstract Query createCountQuery(Object[] values);
protected TypedQuery<Long> createCountQuery(Object[] values) {
return applyHints(doCreateCountQuery(values), method);
}
/**
* Creates a {@link Query} instance for the given values.
*
* @param values must not be {@literal null}.
* @return
*/
protected abstract Query doCreateQuery(Object[] values);
/**
* Creates a {@link TypedQuery} for counting using the given values.
*
* @param values must not be {@literal null}.
* @return
*/
protected abstract TypedQuery<Long> doCreateCountQuery(Object[] values);
}

View File

@@ -15,9 +15,12 @@
*/
package org.springframework.data.jpa.repository.query;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
@@ -42,7 +45,6 @@ public abstract class JpaQueryExecution {
* @param binder
* @return
*/
public Object execute(AbstractJpaQuery query, Object[] values) {
Assert.notNull(query);
@@ -71,7 +73,6 @@ public abstract class JpaQueryExecution {
@Override
protected Object doExecute(AbstractJpaQuery query, Object[] values) {
return query.createQuery(values).getResultList();
}
}
@@ -93,11 +94,11 @@ public abstract class JpaQueryExecution {
protected Object doExecute(AbstractJpaQuery repositoryQuery, Object[] values) {
// Execute query to compute total
Query projection = repositoryQuery.createCountQuery(values);
Long total = (Long) projection.getSingleResult();
TypedQuery<Long> projection = repositoryQuery.createCountQuery(values);
List<Long> totals = projection.getResultList();
Long total = totals.size() == 1 ? totals.get(0) : totals.size();
Query query = repositoryQuery.createQuery(values);
ParameterAccessor accessor = new ParametersParameterAccessor(parameters, values);
return new PageImpl<Object>(query.getResultList(), accessor.getPageable(), total);

View File

@@ -17,6 +17,7 @@ package org.springframework.data.jpa.repository.query;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -101,15 +102,10 @@ final class NamedQuery extends AbstractJpaQuery {
/*
* (non-Javadoc)
*
* @see
* org.springframework.data.jpa.repository.query.AbstractStringBasedJpaQuery
* #
* createQuery(org.springframework.data.jpa.repository.query.ParameterBinder
* )
* @see org.springframework.data.jpa.repository.query.AbstractJpaQuery#doCreateQuery(java.lang.Object[])
*/
@Override
protected Query createQuery(Object[] values) {
protected Query doCreateQuery(Object[] values) {
Query query = getEntityManager().createNamedQuery(queryName);
return createBinder(values).bindAndPrepare(query);
@@ -117,18 +113,15 @@ final class NamedQuery extends AbstractJpaQuery {
/*
* (non-Javadoc)
*
* @see
* org.springframework.data.jpa.repository.query.AbstractStringBasedJpaQuery
* #createCountQuery(org.springframework.data.jpa.repository.query.
* ParameterBinder)
* @see org.springframework.data.jpa.repository.query.AbstractJpaQuery#doCreateCountQuery(java.lang.Object[])
*/
@Override
protected Query createCountQuery(Object[] values) {
protected TypedQuery<Long> doCreateCountQuery(Object[] values) {
Query query = createQuery(values);
String queryString = extractor.extractQueryString(query);
return createBinder(values).bind(getEntityManager().createQuery(QueryUtils.createCountQueryFor(queryString)));
return createBinder(values).bind(
getEntityManager().createQuery(QueryUtils.createCountQueryFor(queryString), Long.class));
}
}

View File

@@ -95,7 +95,7 @@ public class ParameterBinder {
* @param query
* @return
*/
public Query bind(Query query) {
public <T extends Query> T bind(T query) {
int methodParameterPosition = 0;
int queryParameterPosition = 1;

View File

@@ -62,28 +62,23 @@ public class PartTreeJpaQuery extends AbstractJpaQuery {
/*
* (non-Javadoc)
*
* @see
* org.springframework.data.jpa.repository.query.AbstractJpaQuery#createQuery
* (javax.persistence.EntityManager,
* org.springframework.data.jpa.repository.query.ParameterBinder)
* @see org.springframework.data.jpa.repository.query.AbstractJpaQuery#doCreateQuery(java.lang.Object[])
*/
@Override
public Query createQuery(Object[] values) {
public Query doCreateQuery(Object[] values) {
return query.createQuery(values);
}
/*
* (non-Javadoc)
*
* @see org.springframework.data.jpa.repository.query.AbstractJpaQuery#
* createCountQuery(javax.persistence.EntityManager)
* @see org.springframework.data.jpa.repository.query.AbstractJpaQuery#doCreateCountQuery(java.lang.Object[])
*/
@Override
public Query createCountQuery(Object[] values) {
@SuppressWarnings("unchecked")
public TypedQuery<Long> doCreateCountQuery(Object[] values) {
return countQuery.createQuery(values);
return (TypedQuery<Long>) countQuery.createQuery(values);
}
/**

View File

@@ -15,11 +15,9 @@
*/
package org.springframework.data.jpa.repository.query;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.QueryHint;
import javax.persistence.TypedQuery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -42,7 +40,6 @@ final class SimpleJpaQuery extends AbstractJpaQuery {
private final String queryString;
private final String countQuery;
private final String alias;
private final List<QueryHint> hints;
private final Parameters parameters;
/**
@@ -53,7 +50,6 @@ final class SimpleJpaQuery extends AbstractJpaQuery {
super(method, em);
this.queryString = queryString;
this.alias = QueryUtils.detectAlias(queryString);
this.hints = method.getHints();
this.parameters = method.getParameters();
this.countQuery = method.getCountQuery() == null ? QueryUtils.createCountQueryFor(queryString) : method
.getCountQuery();
@@ -64,48 +60,26 @@ final class SimpleJpaQuery extends AbstractJpaQuery {
/*
* (non-Javadoc)
*
* @see
* org.springframework.data.jpa.repository.query.AbstractStringBasedJpaQuery
* #
* createQuery(org.springframework.data.jpa.repository.query.ParameterBinder
* )
* @see org.springframework.data.jpa.repository.query.AbstractJpaQuery#createQuery(java.lang.Object[])
*/
@Override
public Query createQuery(Object[] values) {
public Query doCreateQuery(Object[] values) {
ParameterAccessor accessor = new ParametersParameterAccessor(parameters, values);
String sortedQueryString = QueryUtils.applySorting(queryString, accessor.getSort(), alias);
Query query = getEntityManager().createQuery(sortedQueryString);
return createBinder(values).bindAndPrepare(applyHints(query));
return createBinder(values).bindAndPrepare(query);
}
/*
* (non-Javadoc)
*
* @see org.springframework.data.jpa.repository.query.AbstractJpaQuery#
* createCountQuery(java.lang.Object[])
* @see org.springframework.data.jpa.repository.query.AbstractJpaQuery#doCreateCountQuery(java.lang.Object[])
*/
@Override
protected Query createCountQuery(Object[] values) {
protected TypedQuery<Long> doCreateCountQuery(Object[] values) {
return createBinder(values).bind(applyHints(getEntityManager().createQuery(countQuery)));
}
/**
* Applies the declared query hints to the given query.
*
* @param query
* @return
*/
private Query applyHints(Query query) {
for (QueryHint hint : hints) {
query.setHint(hint.name(), hint.value());
}
return query;
return createBinder(values).bind(getEntityManager().createQuery(countQuery, Long.class));
}
/**

View File

@@ -725,6 +725,18 @@ public class UserRepositoryTests {
assertThat(result, hasItem(firstUser));
}
/**
* @see DATADOC-86
*/
@Test
public void readsPageWithGroupByClauseCorrectly() {
flushTestUsers();
Page<String> result = repository.findByLastnameGrouped(new PageRequest(0, 10));
assertThat(result.getTotalPages(), is(1));
}
private Page<User> executeSpecWithSort(Sort sort) {
flushTestUsers();

View File

@@ -30,7 +30,7 @@ import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.jpa.repository.query.JpaQueryExecution.ModifyingExecution;
/**
* Unit test for {@link QueryExecution}.
* Unit test for {@link JpaQueryExecution}.
*
* @author Oliver Gierke
*/

View File

@@ -23,8 +23,7 @@ import static org.mockito.Mockito.*;
import java.lang.reflect.Method;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.QueryHint;
import javax.persistence.TypedQuery;
import org.junit.Before;
import org.junit.Test;
@@ -34,7 +33,6 @@ import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.QueryHints;
import org.springframework.data.jpa.repository.sample.UserRepository;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.Parameters;
@@ -54,31 +52,22 @@ public class SimpleJpaQueryUnitTests {
@Mock
QueryExtractor extractor;
@Mock
Query query;
TypedQuery<Long> query;
@Mock
RepositoryMetadata metadata;
@Mock
ParameterBinder binder;
@Before
@QueryHints(@QueryHint(name = "foo", value = "bar"))
public void setUp() throws SecurityException, NoSuchMethodException {
when(em.createQuery(anyString())).thenReturn(query);
when(em.createQuery(anyString(), eq(Long.class))).thenReturn(query);
Method setUp = UserRepository.class.getMethod("findByLastname", String.class);
method = new JpaQueryMethod(setUp, metadata, extractor);
}
@Test
public void appliesHintsCorrectly() throws Exception {
SimpleJpaQuery jpaQuery = new SimpleJpaQuery(method, em, "foobar");
jpaQuery.createQuery(new Object[] { "gierke" });
verify(query).setHint("foo", "bar");
}
@Test
public void prefersDeclaredCountQueryOverCreatingOne() throws Exception {
@@ -86,7 +75,7 @@ public class SimpleJpaQueryUnitTests {
when(method.getCountQuery()).thenReturn("foo");
when(method.getParameters()).thenReturn(
new Parameters(SimpleJpaQueryUnitTests.class.getMethod("prefersDeclaredCountQueryOverCreatingOne")));
when(em.createQuery("foo")).thenReturn(query);
when(em.createQuery("foo", Long.class)).thenReturn(query);
SimpleJpaQuery jpaQuery = new SimpleJpaQuery(method, em, "select u from User u");

View File

@@ -185,4 +185,9 @@ public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecifi
List<User> findByLastnameAndFirstnameAllIgnoringCase(String lastname, String firstname);
/**
* @see DATADOC-86 - Count execution for group by
*/
@Query("select u.lastname from User u group by u.lastname")
Page<String> findByLastnameGrouped(Pageable pageable);
}