Apply hints for pagined projections on fetchable Querydsl queries.

When using Querydsl predicates to build a fluent query, be sure to apply the projection hint that generates a fetch graph.

Resolves #2820.
Original pull request: #2827.
This commit is contained in:
Greg L. Turnquist
2023-02-27 11:33:52 -06:00
committed by Mark Paluch
parent aab0efa5df
commit 752fe463ed
4 changed files with 76 additions and 26 deletions

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.data.jpa.repository.support;
import jakarta.persistence.EntityManager;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -23,8 +25,6 @@ import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Stream;
import jakarta.persistence.EntityManager;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
@@ -69,8 +69,7 @@ class FetchableFluentQueryByPredicate<S, R> extends FluentQuerySupport<S, R> imp
private FetchableFluentQueryByPredicate(Predicate predicate, Class<S> entityType, Class<R> resultType, Sort sort,
Collection<String> properties, Function<Sort, AbstractJPAQuery<?, ?>> finder,
BiFunction<Sort, Pageable, AbstractJPAQuery<?, ?>> pagedFinder, Function<Predicate, Long> countOperation,
Function<Predicate, Boolean> existsOperation,
EntityManager entityManager) {
Function<Predicate, Boolean> existsOperation, EntityManager entityManager) {
super(resultType, sort, properties, entityType);
this.predicate = predicate;
@@ -175,8 +174,13 @@ class FetchableFluentQueryByPredicate<S, R> extends FluentQuerySupport<S, R> imp
private Page<R> readPage(Pageable pageable) {
AbstractJPAQuery<?, ?> pagedQuery = pagedFinder.apply(sort, pageable);
List<R> paginatedResults = convert(pagedQuery.fetch());
AbstractJPAQuery<?, ?> query = pagedFinder.apply(sort, pageable);
if (!properties.isEmpty()) {
query.setHint(EntityGraphFactory.HINT, EntityGraphFactory.create(entityManager, entityType, properties));
}
List<R> paginatedResults = convert(query.fetch());
return PageableExecutionUtils.getPage(paginatedResults, pageable, () -> countOperation.apply(predicate));
}

View File

@@ -15,18 +15,17 @@
*/
package org.springframework.data.jpa.repository;
import static java.util.Arrays.asList;
import static java.util.Arrays.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.springframework.data.domain.Example.of;
import static org.springframework.data.domain.Example.*;
import static org.springframework.data.domain.ExampleMatcher.GenericPropertyMatcher;
import static org.springframework.data.domain.ExampleMatcher.StringMatcher;
import static org.springframework.data.domain.ExampleMatcher.matching;
import static org.springframework.data.domain.Sort.Direction.ASC;
import static org.springframework.data.domain.Sort.Direction.DESC;
import static org.springframework.data.domain.Sort.Direction.*;
import static org.springframework.data.jpa.domain.Specification.*;
import static org.springframework.data.jpa.domain.Specification.not;
import static org.springframework.data.jpa.domain.Specification.where;
import static org.springframework.data.jpa.domain.sample.UserSpecifications.userHasAgeLess;
import static org.springframework.data.jpa.domain.sample.UserSpecifications.userHasFirstname;
import static org.springframework.data.jpa.domain.sample.UserSpecifications.userHasFirstnameLike;
@@ -75,6 +74,7 @@ import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.domain.sample.Address;
import org.springframework.data.jpa.domain.sample.QUser;
import org.springframework.data.jpa.domain.sample.Role;
import org.springframework.data.jpa.domain.sample.SpecialUser;
import org.springframework.data.jpa.domain.sample.User;
@@ -2444,6 +2444,47 @@ class UserRepositoryTests {
);
}
@Test // GH-2820
void findByFluentPredicateWithProjectionAndPageRequest() {
flushTestUsers();
em.clear();
Page<User> users = repository.findBy(QUser.user.firstname.contains("v"), q -> q //
.project("firstname") //
.page(PageRequest.of(0, 10)));
assertThat(users).extracting(User::getFirstname).containsExactlyInAnyOrder(firstUser.getFirstname(),
thirdUser.getFirstname(), fourthUser.getFirstname());
}
@Test // GH-2820
void findByFluentPredicateWithProjectionAndAll() {
flushTestUsers();
em.clear();
List<User> users = repository.findBy(QUser.user.firstname.contains("v"), q -> q //
.project("firstname") //
.all());
assertThat(users).extracting(User::getFirstname).containsExactlyInAnyOrder(firstUser.getFirstname(),
thirdUser.getFirstname(), fourthUser.getFirstname());
}
@Test // GH-2820
void findByFluentPredicateWithPageRequest() {
flushTestUsers();
em.clear();
Page<User> users = repository.findBy(QUser.user.firstname.contains("v"), q -> q //
.page(PageRequest.of(0, 10)));
assertThat(users).extracting(User::getFirstname).containsExactlyInAnyOrder(firstUser.getFirstname(),
thirdUser.getFirstname(), fourthUser.getFirstname());
}
@Test // GH-2274
void findByFluentSpecificationWithCollectionPropertyPathsDoesntLoadUnrequestedPaths() {

View File

@@ -18,15 +18,29 @@ package org.springframework.data.jpa.repository.sample;
import jakarta.persistence.EntityManager;
import jakarta.persistence.QueryHint;
import java.util.*;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import org.springframework.data.domain.*;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.sample.Role;
import org.springframework.data.jpa.domain.sample.SpecialUser;
import org.springframework.data.jpa.domain.sample.User;
import org.springframework.data.jpa.repository.*;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.QueryHints;
import org.springframework.data.jpa.repository.query.Procedure;
import org.springframework.data.querydsl.ListQuerydslPredicateExecutor;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
@@ -45,8 +59,8 @@ import org.springframework.transaction.annotation.Transactional;
* @author Diego Krupitza
* @author Geoffrey Deremetz
*/
public interface UserRepository
extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User>, UserRepositoryCustom {
public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User>,
UserRepositoryCustom, ListQuerydslPredicateExecutor<User> {
/**
* Retrieve users by their lastname. The finder {@literal User.findByLastname} is declared in

View File

@@ -32,8 +32,6 @@ import org.springframework.data.querydsl.EntityPathResolver;
import org.springframework.data.querydsl.SimpleEntityPathResolver;
import org.springframework.test.util.ReflectionTestUtils;
import com.querydsl.core.types.EntityPath;
/**
* Unit tests for {@link EntityPathResolver} related tests on {@link JpaRepositoryFactoryBean}.
*
@@ -47,14 +45,7 @@ class JpaRepositoryFactoryBeanEntityPathResolverIntegrationTests {
@EnableJpaRepositories(basePackageClasses = UserRepository.class, //
includeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = UserRepository.class))
static class BaseConfig {
static final EntityPathResolver RESOLVER = new EntityPathResolver() {
@Override
public <T> EntityPath<T> createPath(Class<T> domainClass) {
return null;
}
};
static final EntityPathResolver RESOLVER = SimpleEntityPathResolver.INSTANCE;
}
@Configuration