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