DATAJPA-124 - Optimized pagination execution.

For pagination we already need to trigger a count query to find out the total number of pages available. Now if there are less elements available than the offset of the current page points to we don't need to trigger the actual content reading query at all. E.g. if there's only 20 elements in the database and we request page 3 by a page size of 10 we already know that there won't be any elements found.

Implemented that optimization for general CRUD pagination as well as pagination in query methods.
This commit is contained in:
Tomasz Nurkiewicz
2011-11-10 09:53:43 +01:00
committed by Oliver Gierke
parent e3da457137
commit d49d4d5f0a
4 changed files with 121 additions and 2 deletions

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.data.jpa.repository.query;
import java.util.Collections;
import java.util.List;
import javax.persistence.EntityManager;
@@ -24,6 +25,7 @@ import javax.persistence.TypedQuery;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.ParametersParameterAccessor;
@@ -100,8 +102,11 @@ public abstract class JpaQueryExecution {
Query query = repositoryQuery.createQuery(values);
ParameterAccessor accessor = new ParametersParameterAccessor(parameters, values);
Pageable pageable = accessor.getPageable();
return new PageImpl<Object>(query.getResultList(), accessor.getPageable(), total);
List<Object> content = total > pageable.getOffset() ? query.getResultList() : Collections.emptyList();
return new PageImpl<Object>(content, pageable, total);
}
}

View File

@@ -19,6 +19,7 @@ import static org.springframework.data.jpa.repository.query.QueryUtils.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.persistence.EntityManager;
@@ -352,8 +353,9 @@ public class SimpleJpaRepository<T, ID extends Serializable> implements JpaRepos
query.setMaxResults(pageable.getPageSize());
Long total = getCountQuery(spec).getSingleResult();
List<T> content = total > pageable.getOffset() ? query.getResultList() : Collections.<T> emptyList();
return new PageImpl<T>(query.getResultList(), pageable, total);
return new PageImpl<T>(content, pageable, total);
}
/**

View File

@@ -19,15 +19,22 @@ import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.util.Arrays;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
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.query.JpaQueryExecution.ModifyingExecution;
import org.springframework.data.jpa.repository.query.JpaQueryExecution.PagedExecution;
import org.springframework.data.repository.query.Parameters;
/**
* Unit test for {@link JpaQueryExecution}.
@@ -46,6 +53,9 @@ public class JpaQueryExecutionUnitTests {
@Mock
JpaQueryMethod method;
@Mock
TypedQuery<Long> countQuery;
@Test(expected = IllegalArgumentException.class)
public void rejectsNullQuery() {
@@ -104,6 +114,27 @@ public class JpaQueryExecutionUnitTests {
new ModifyingExecution(method, em);
}
/**
* @see DATAJPA-124
*/
@Test
public void pagedExecutionDoesNotRetrieveObjectsForPageableOutOfRange() throws Exception {
Parameters parameters = new Parameters(getClass().getMethod("sampleMethod", Pageable.class));
when(jpaQuery.createCountQuery(Mockito.any(Object[].class))).thenReturn(countQuery);
when(jpaQuery.createQuery(Mockito.any(Object[].class))).thenReturn(query);
when(countQuery.getResultList()).thenReturn(Arrays.asList(20L));
PagedExecution execution = new PagedExecution(parameters);
execution.doExecute(jpaQuery, new Object[] { new PageRequest(2, 10) });
verify(query, times(0)).getResultList();
}
public static void sampleMethod(Pageable pageable) {
}
static class StubQueryExecution extends JpaQueryExecution {
@Override

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2011 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.repository.support;
import static org.mockito.Mockito.*;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.domain.sample.User;
/**
* Unit tests for {@link SimpleJpaRepository}.
*
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class SimpleJpaRepositoryUnitTests {
SimpleJpaRepository<User, Long> repo;
@Mock
EntityManager em;
@Mock
CriteriaBuilder builder;
@Mock
CriteriaQuery<User> criteriaQuery;
@Mock
CriteriaQuery<Long> countCriteriaQuery;
@Mock
TypedQuery<User> query;
@Mock
TypedQuery<Long> countQuery;
@Mock
JpaEntityInformation<User, Long> information;
@Before
public void setUp() {
when(information.getJavaType()).thenReturn(User.class);
when(em.getCriteriaBuilder()).thenReturn(builder);
when(builder.createQuery(User.class)).thenReturn(criteriaQuery);
when(builder.createQuery(Long.class)).thenReturn(countCriteriaQuery);
when(em.createQuery(criteriaQuery)).thenReturn(query);
when(em.createQuery(countCriteriaQuery)).thenReturn(countQuery);
repo = new SimpleJpaRepository<User, Long>(information, em);
}
@Test
public void doesNotActuallyRetrieveObjectsForPageableOutOfRange() {
when(countQuery.getSingleResult()).thenReturn(20L);
repo.findAll(new PageRequest(2, 10));
verify(query, times(0)).getResultList();
}
}