Skip to content

Commit

Permalink
Apply hints for pagined projections on fetchable Querydsl queries.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
gregturn authored and mp911de committed Mar 3, 2023
1 parent dbb92d3 commit f0c7819
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -23,8 +25,6 @@
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;
Expand Down Expand Up @@ -69,8 +69,7 @@ public FetchableFluentQueryByPredicate(Predicate predicate, Class<S> entityType,
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;
Expand Down Expand Up @@ -175,8 +174,13 @@ public boolean exists() {

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));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,13 @@
*/
package org.springframework.data.jpa.repository;

import static java.util.Arrays.asList;
import static java.util.Arrays.*;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.domain.Example.of;
import static org.springframework.data.domain.Example.*;
import static org.springframework.data.domain.ExampleMatcher.*;
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.*;

import jakarta.persistence.EntityManager;
Expand Down Expand Up @@ -53,6 +52,7 @@
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;
Expand Down Expand Up @@ -2421,6 +2421,47 @@ void findByFluentSpecificationWithSimplePropertyPathsDoesntLoadUnrequestedPaths(
);
}

@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() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,29 @@
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;
Expand All @@ -45,8 +59,8 @@
* @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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@
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}.
*
Expand All @@ -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
Expand Down

0 comments on commit f0c7819

Please sign in to comment.