From d49d4d5f0a96148be3ef7946e95b1a44fba1f580 Mon Sep 17 00:00:00 2001 From: Tomasz Nurkiewicz Date: Thu, 10 Nov 2011 09:53:43 +0100 Subject: [PATCH] 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. --- .../repository/query/JpaQueryExecution.java | 7 +- .../support/SimpleJpaRepository.java | 4 +- .../query/JpaQueryExecutionUnitTests.java | 31 +++++++ .../support/SimpleJpaRepositoryUnitTests.java | 81 +++++++++++++++++++ 4 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 src/test/java/org/springframework/data/jpa/repository/support/SimpleJpaRepositoryUnitTests.java diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java index 3125e8430..ad9f2e9a9 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java @@ -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(query.getResultList(), accessor.getPageable(), total); + List content = total > pageable.getOffset() ? query.getResultList() : Collections.emptyList(); + + return new PageImpl(content, pageable, total); } } diff --git a/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java b/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java index 9bb84de35..65d019e19 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java @@ -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 implements JpaRepos query.setMaxResults(pageable.getPageSize()); Long total = getCountQuery(spec).getSingleResult(); + List content = total > pageable.getOffset() ? query.getResultList() : Collections. emptyList(); - return new PageImpl(query.getResultList(), pageable, total); + return new PageImpl(content, pageable, total); } /** diff --git a/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryExecutionUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryExecutionUnitTests.java index df37a3d57..e8f24ef97 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryExecutionUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryExecutionUnitTests.java @@ -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 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 diff --git a/src/test/java/org/springframework/data/jpa/repository/support/SimpleJpaRepositoryUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/support/SimpleJpaRepositoryUnitTests.java new file mode 100644 index 000000000..d14ea82ea --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/repository/support/SimpleJpaRepositoryUnitTests.java @@ -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 repo; + + @Mock + EntityManager em; + @Mock + CriteriaBuilder builder; + @Mock + CriteriaQuery criteriaQuery; + @Mock + CriteriaQuery countCriteriaQuery; + @Mock + TypedQuery query; + @Mock + TypedQuery countQuery; + @Mock + JpaEntityInformation 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(information, em); + } + + @Test + public void doesNotActuallyRetrieveObjectsForPageableOutOfRange() { + + when(countQuery.getSingleResult()).thenReturn(20L); + repo.findAll(new PageRequest(2, 10)); + + verify(query, times(0)).getResultList(); + } +}