From cbc2224ef1667167331e2f84310e1391d7aee5b6 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Tue, 12 Dec 2017 13:05:14 +0100 Subject: [PATCH] DATAJPA-1234 - Refactored Querydsl support to be based on fragments. The base class for repositories is now fixed. Additional features to support Querydsl come in via QuerydslJpaPredicateExecutor which is based on the QuerydslJpaRepository. The QuerydslJpaRepository is deprecated and exists only for backward compatibility. --- .../support/JpaRepositoryFactory.java | 37 +- .../support/QuerydslJpaPredicateExecutor.java | 255 ++++++++++++++ .../support/QuerydslJpaRepository.java | 4 + .../JpaRepositoryFactoryBeanUnitTests.java | 2 +- .../JpaRepositoryFactoryUnitTests.java | 24 +- ...QuerydslJpaPredicateExecutorUnitTests.java | 317 ++++++++++++++++++ .../support/TransactionalRepositoryTests.java | 2 +- 7 files changed, 614 insertions(+), 27 deletions(-) create mode 100644 src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java create mode 100644 src/test/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutorUnitTests.java diff --git a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java index 099588232..d8c80058d 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java @@ -23,6 +23,7 @@ import java.util.Optional; import javax.persistence.EntityManager; import org.springframework.beans.factory.BeanFactory; +import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.jpa.projection.CollectionAwareProjectionFactory; import org.springframework.data.jpa.provider.PersistenceProvider; import org.springframework.data.jpa.provider.QueryExtractor; @@ -30,9 +31,12 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.data.querydsl.SimpleEntityPathResolver; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.core.support.RepositoryComposition; import org.springframework.data.repository.core.support.RepositoryFactorySupport; +import org.springframework.data.repository.core.support.RepositoryFragment; import org.springframework.data.repository.query.EvaluationContextProvider; import org.springframework.data.repository.query.QueryLookupStrategy; import org.springframework.data.repository.query.QueryLookupStrategy.Key; @@ -118,12 +122,7 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport { */ @Override protected Class getRepositoryBaseClass(RepositoryMetadata metadata) { - - if (isQueryDslExecutor(metadata.getRepositoryInterface())) { - return QuerydslJpaRepository.class; - } else { - return SimpleJpaRepository.class; - } + return SimpleJpaRepository.class; } /* @@ -171,4 +170,30 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport { return (JpaEntityInformation) JpaEntityInformationSupport.getEntityInformation(domainClass, entityManager); } + + @Override + protected RepositoryComposition.RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) { + + RepositoryComposition.RepositoryFragments fragments = RepositoryComposition.RepositoryFragments.empty(); + + boolean isQueryDslRepository = QUERY_DSL_PRESENT + && QuerydslPredicateExecutor.class.isAssignableFrom(metadata.getRepositoryInterface()); + + if (isQueryDslRepository) { + + if (metadata.isReactiveRepository()) { + throw new InvalidDataAccessApiUsageException( + "Cannot combine Querydsl and reactive repository support in a single interface"); + } + + JpaEntityInformation entityInformation = getEntityInformation(metadata.getDomainType()); + + Object querydslFragment = getTargetRepositoryViaReflection(QuerydslJpaPredicateExecutor.class, entityInformation, entityManager, + SimpleEntityPathResolver.INSTANCE, crudMethodMetadataPostProcessor.getCrudMethodMetadata()); + + fragments = fragments.append(RepositoryFragment.implemented(querydslFragment)); + } + + return fragments; + } } diff --git a/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java b/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java new file mode 100644 index 000000000..ef72c188f --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java @@ -0,0 +1,255 @@ +/* + * Copyright 2008-2017 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 java.util.List; +import java.util.Map.Entry; +import java.util.Optional; + +import javax.persistence.EntityManager; +import javax.persistence.LockModeType; + +import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.querydsl.EntityPathResolver; +import org.springframework.data.querydsl.QSort; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.data.querydsl.SimpleEntityPathResolver; +import org.springframework.data.repository.support.PageableExecutionUtils; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +import com.querydsl.core.NonUniqueResultException; +import com.querydsl.core.types.EntityPath; +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.dsl.PathBuilder; +import com.querydsl.jpa.JPQLQuery; +import com.querydsl.jpa.impl.AbstractJPAQuery; + +/** + * QueryDsl specific fragment for extending {@link SimpleJpaRepository} with an implementation for implementation for + * {@link QuerydslPredicateExecutor}. + * + * @author Oliver Gierke + * @author Thomas Darimont + * @author Mark Paluch + * @author Jocelyn Ntakpe + * @author Christoph Strobl + * @author Jens Schauder + */ +public class QuerydslJpaPredicateExecutor implements QuerydslPredicateExecutor { + + private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE; + + private final JpaEntityInformation entityInformation; + private final EntityPath path; + private final Querydsl querydsl; + private final EntityManager entityManager; + private final CrudMethodMetadata metadata; + + /** + * Creates a new {@link QuerydslJpaPredicateExecutor} from the given domain class and {@link EntityManager} and uses the + * given {@link EntityPathResolver} to translate the domain class into an {@link EntityPath}. + * @param entityInformation must not be {@literal null}. + * @param entityManager must not be {@literal null}. + * @param resolver must not be {@literal null}. + * @param metadata maybe {@literal null}. + */ + public QuerydslJpaPredicateExecutor(JpaEntityInformation entityInformation, EntityManager entityManager, + EntityPathResolver resolver, @Nullable CrudMethodMetadata metadata) { + + this.entityInformation = entityInformation; + this.metadata = metadata; + this.path = resolver.createPath(entityInformation.getJavaType()); + this.querydsl = new Querydsl(entityManager, new PathBuilder(path.getType(), path.getMetadata())); + this.entityManager = entityManager; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.querydsl.QuerydslPredicateExecutor#findOne(com.mysema.query.types.Predicate) + */ + @Override + public Optional findOne(Predicate predicate) { + + try { + return Optional.ofNullable(createQuery(predicate).select(path).fetchOne()); + } catch (NonUniqueResultException ex) { + throw new IncorrectResultSizeDataAccessException(ex.getMessage(), 1, ex); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.data.querydsl.QuerydslPredicateExecutor#findAll(com.mysema.query.types.Predicate) + */ + @Override + public List findAll(Predicate predicate) { + return createQuery(predicate).select(path).fetch(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.querydsl.QuerydslPredicateExecutor#findAll(com.mysema.query.types.Predicate, com.mysema.query.types.OrderSpecifier[]) + */ + @Override + public List findAll(Predicate predicate, OrderSpecifier... orders) { + return executeSorted(createQuery(predicate).select(path), orders); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.querydsl.QuerydslPredicateExecutor#findAll(com.mysema.query.types.Predicate, org.springframework.data.domain.Sort) + */ + @Override + public List findAll(Predicate predicate, Sort sort) { + + Assert.notNull(sort, "Sort must not be null!"); + + return executeSorted(createQuery(predicate).select(path), sort); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.querydsl.QuerydslPredicateExecutor#findAll(com.mysema.query.types.OrderSpecifier[]) + */ + @Override + public List findAll(OrderSpecifier... orders) { + + Assert.notNull(orders, "Order specifiers must not be null!"); + + return executeSorted(createQuery(new Predicate[0]).select(path), orders); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.querydsl.QuerydslPredicateExecutor#findAll(com.querydsl.core.types.Predicate, org.springframework.data.domain.Pageable) + */ + @Override + public Page findAll(Predicate predicate, Pageable pageable) { + + Assert.notNull(pageable, "Pageable must not be null!"); + + final JPQLQuery countQuery = createCountQuery(predicate); + JPQLQuery query = querydsl.applyPagination(pageable, createQuery(predicate).select(path)); + + return PageableExecutionUtils.getPage(query.fetch(), pageable, countQuery::fetchCount); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.querydsl.QueryDslPredicateExecutor#count(com.mysema.query.types.Predicate) + */ + @Override + public long count(Predicate predicate) { + return createQuery(predicate).fetchCount(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.querydsl.QueryDslPredicateExecutor#exists(com.mysema.query.types.Predicate) + */ + @Override + public boolean exists(Predicate predicate) { + return createQuery(predicate).fetchCount() > 0; + } + + /** + * Creates a new {@link JPQLQuery} for the given {@link Predicate}. + * + * @param predicate + * @return the Querydsl {@link JPQLQuery}. + */ + protected JPQLQuery createQuery(Predicate... predicate) { + + AbstractJPAQuery query = doCreateQuery(getQueryHints().withFetchGraphs(entityManager), predicate); + + CrudMethodMetadata metadata = getRepositoryMethodMetadata(); + + if (metadata == null) { + return query; + } + + LockModeType type = metadata.getLockModeType(); + return type == null ? query : query.setLockMode(type); + } + + /** + * Creates a new {@link JPQLQuery} count query for the given {@link Predicate}. + * + * @param predicate, can be {@literal null}. + * @return the Querydsl count {@link JPQLQuery}. + */ + protected JPQLQuery createCountQuery(@Nullable Predicate... predicate) { + return doCreateQuery(getQueryHints(), predicate); + } + + @Nullable + private CrudMethodMetadata getRepositoryMethodMetadata() { + return metadata; + } + + /** + * Returns {@link QueryHints} with the query hints based on the current {@link CrudMethodMetadata} and potential + * {@link EntityGraph} information. + * + * @return + */ + private QueryHints getQueryHints() { + return metadata == null ? QueryHints.NoHints.INSTANCE : DefaultQueryHints.of(entityInformation, metadata); + } + + private AbstractJPAQuery doCreateQuery(QueryHints hints, @Nullable Predicate... predicate) { + + AbstractJPAQuery query = querydsl.createQuery(path); + + if (predicate != null) { + query = query.where(predicate); + } + + for (Entry hint : hints) { + query.setHint(hint.getKey(), hint.getValue()); + } + + return query; + } + + /** + * Executes the given {@link JPQLQuery} after applying the given {@link OrderSpecifier}s. + * + * @param query must not be {@literal null}. + * @param orders must not be {@literal null}. + * @return + */ + private List executeSorted(JPQLQuery query, OrderSpecifier... orders) { + return executeSorted(query, new QSort(orders)); + } + + /** + * Executes the given {@link JPQLQuery} after applying the given {@link Sort}. + * + * @param query must not be {@literal null}. + * @param sort must not be {@literal null}. + * @return + */ + private List executeSorted(JPQLQuery query, Sort sort) { + return querydsl.applySorting(sort, query).fetch(); + } +} diff --git a/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaRepository.java b/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaRepository.java index 9dddf9926..e450b165e 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaRepository.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaRepository.java @@ -47,12 +47,16 @@ import com.querydsl.jpa.impl.AbstractJPAQuery; * QueryDsl specific extension of {@link SimpleJpaRepository} which adds implementation for * {@link QuerydslPredicateExecutor}. * + * @deprecated Instead of this class use {@link QuerydslJpaPredicateExecutor} + * * @author Oliver Gierke * @author Thomas Darimont * @author Mark Paluch * @author Jocelyn Ntakpe * @author Christoph Strobl + * @author Jens Schauder */ +@Deprecated public class QuerydslJpaRepository extends SimpleJpaRepository implements QuerydslPredicateExecutor { diff --git a/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBeanUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBeanUnitTests.java index 21f71f856..7b5d685fe 100644 --- a/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBeanUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBeanUnitTests.java @@ -118,7 +118,7 @@ public class JpaRepositoryFactoryBeanUnitTests { /* * (non-Javadoc) - * @see org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean#doCreateRepositoryFactory() + * @see org.springframework.data.jpa.predicateExecutor.support.JpaRepositoryFactoryBean#doCreateRepositoryFactory() */ @Override protected RepositoryFactorySupport doCreateRepositoryFactory() { diff --git a/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryUnitTests.java index 100efaae8..53e583f1e 100644 --- a/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryUnitTests.java @@ -50,6 +50,7 @@ import org.springframework.util.ClassUtils; * * @author Oliver Gierke * @author Thomas Darimont + * @author Jens Schauder */ @RunWith(MockitoJUnitRunner.Silent.class) public class JpaRepositoryFactoryUnitTests { @@ -104,9 +105,9 @@ public class JpaRepositoryFactoryUnitTests { } /** - * Asserts that the factory recognized configured repository classes that contain custom method but no custom - * implementation could be found. Furthremore the exception has to contain the name of the repository interface as for - * a large repository configuration it's hard to find out where this error occured. + * Asserts that the factory recognized configured predicateExecutor classes that contain custom method but no custom + * implementation could be found. Furthremore the exception has to contain the name of the predicateExecutor interface as for + * a large predicateExecutor configuration it's hard to find out where this error occured. * * @throws Exception */ @@ -144,21 +145,6 @@ public class JpaRepositoryFactoryUnitTests { repository.customMethod(1); } - @Test - public void usesQueryDslRepositoryIfInterfaceImplementsExecutor() { - - when(entityInformation.getJavaType()).thenReturn(User.class); - assertEquals(QuerydslJpaRepository.class, - factory.getRepositoryBaseClass(new DefaultRepositoryMetadata(QueryDslSampleRepository.class))); - - try { - QueryDslSampleRepository repository = factory.getRepository(QueryDslSampleRepository.class); - assertEquals(QuerydslJpaRepository.class, ((Advised) repository).getTargetClass()); - } catch (IllegalArgumentException e) { - assertThat(e.getStackTrace()[0].getClassName(), is("org.springframework.data.querydsl.SimpleEntityPathResolver")); - } - } - @Test // DATAJPA-710, DATACMNS-542 public void usesConfiguredRepositoryBaseClass() { @@ -198,7 +184,7 @@ public class JpaRepositoryFactoryUnitTests { } /** - * Implementation of the custom repository interface. + * Implementation of the custom predicateExecutor interface. * * @author Oliver Gierke */ diff --git a/src/test/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutorUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutorUnitTests.java new file mode 100644 index 000000000..cc22045ba --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutorUnitTests.java @@ -0,0 +1,317 @@ +/* + * Copyright 2008-2017 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.assertj.core.api.Assertions.*; + +import java.util.List; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + +import org.joda.time.LocalDate; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.domain.Sort.Order; +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.User; +import org.springframework.data.querydsl.QPageRequest; +import org.springframework.data.querydsl.QSort; +import org.springframework.data.querydsl.SimpleEntityPathResolver; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.annotation.Transactional; + +import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.PathBuilder; +import com.querydsl.core.types.dsl.PathBuilderFactory; + +/** + * Integration test for {@link QuerydslJpaPredicateExecutor}. + * + * @author Jens Schauder + * @author Oliver Gierke + * @author Thomas Darimont + * @author Mark Paluch + * @author Christoph Strobl + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration({ "classpath:infrastructure.xml" }) +@Transactional +public class QuerydslJpaPredicateExecutorUnitTests { + + @PersistenceContext EntityManager em; + + QuerydslJpaPredicateExecutor predicateExecutor; + QUser user = new QUser("user"); + User dave, carter, oliver; + Role adminRole; + + @Before + public void setUp() { + + JpaEntityInformation information = new JpaMetamodelEntityInformation<>(User.class, + em.getMetamodel()); + + SimpleJpaRepository repository = new SimpleJpaRepository<>(information, em); + dave = repository.save(new User("Dave", "Matthews", "dave@matthews.com")); + carter = repository.save(new User("Carter", "Beauford", "carter@beauford.com")); + oliver = repository.save(new User("Oliver", "matthews", "oliver@matthews.com")); + adminRole = em.merge(new Role("admin")); + + this.predicateExecutor = new QuerydslJpaPredicateExecutor<>(information, em, SimpleEntityPathResolver.INSTANCE, null); + } + + @Test + public void executesPredicatesCorrectly() throws Exception { + + BooleanExpression isCalledDave = user.firstname.eq("Dave"); + BooleanExpression isBeauford = user.lastname.eq("Beauford"); + + List result = predicateExecutor.findAll(isCalledDave.or(isBeauford)); + + assertThat(result).containsExactlyInAnyOrder(carter, dave); + } + + @Test + public void executesStringBasedPredicatesCorrectly() throws Exception { + + PathBuilder builder = new PathBuilderFactory().create(User.class); + + BooleanExpression isCalledDave = builder.getString("firstname").eq("Dave"); + BooleanExpression isBeauford = builder.getString("lastname").eq("Beauford"); + + List result = predicateExecutor.findAll(isCalledDave.or(isBeauford)); + + assertThat(result).containsExactlyInAnyOrder(carter, dave); + } + + @Test // DATAJPA-243 + public void considersSortingProvidedThroughPageable() { + + Predicate lastnameContainsE = user.lastname.contains("e"); + + Page result = predicateExecutor.findAll(lastnameContainsE, PageRequest.of(0, 1, Direction.ASC, "lastname")); + + assertThat(result).containsExactly(carter); + + result = predicateExecutor.findAll(lastnameContainsE, PageRequest.of(0, 2, Direction.DESC, "lastname")); + + assertThat(result).containsExactly(oliver, dave); + } + + @Test // DATAJPA-296 + public void appliesIgnoreCaseOrdering() { + + Sort sort = Sort.by(new Order(Direction.DESC, "lastname").ignoreCase(), new Order(Direction.ASC, "firstname")); + + Page result = predicateExecutor.findAll(user.lastname.contains("e"), PageRequest.of(0, 2, sort)); + + assertThat(result.getContent()).containsExactly(dave, oliver); + } + + @Test // DATAJPA-427 + public void findBySpecificationWithSortByPluralAssociationPropertyInPageableShouldUseSortNullValuesLast() { + + oliver.getColleagues().add(dave); + dave.getColleagues().add(oliver); + + QUser user = QUser.user; + + Page page = predicateExecutor.findAll(user.firstname.isNotNull(), + PageRequest.of(0, 10, Sort.by(Sort.Direction.ASC, "colleagues.firstname"))); + + assertThat(page.getContent()).hasSize(3).contains(oliver, dave, carter); + } + + @Test // DATAJPA-427 + public void findBySpecificationWithSortBySingularAssociationPropertyInPageableShouldUseSortNullValuesLast() { + + oliver.setManager(dave); + dave.setManager(carter); + + QUser user = QUser.user; + + Page page = predicateExecutor.findAll(user.firstname.isNotNull(), + PageRequest.of(0, 10, Sort.by(Sort.Direction.ASC, "manager.firstname"))); + + assertThat(page.getContent()).hasSize(3).contains(dave, oliver, carter); + } + + @Test // DATAJPA-427 + public void findBySpecificationWithSortBySingularPropertyInPageableShouldUseSortNullValuesFirst() { + + QUser user = QUser.user; + + Page page = predicateExecutor.findAll(user.firstname.isNotNull(), + PageRequest.of(0, 10, Sort.by(Sort.Direction.ASC, "firstname"))); + + assertThat(page.getContent()).containsExactly(carter, dave, oliver); + } + + @Test // DATAJPA-427 + public void findBySpecificationWithSortByOrderIgnoreCaseBySingularPropertyInPageableShouldUseSortNullValuesFirst() { + + QUser user = QUser.user; + + Page page = predicateExecutor.findAll(user.firstname.isNotNull(), + PageRequest.of(0, 10, Sort.by(new Order(Sort.Direction.ASC, "firstname").ignoreCase()))); + + assertThat(page.getContent()).containsExactly(carter, dave, oliver); + } + + @Test // DATAJPA-427 + public void findBySpecificationWithSortByNestedEmbeddedPropertyInPageableShouldUseSortNullValuesFirst() { + + oliver.setAddress(new Address("Germany", "Saarbrücken", "HaveItYourWay", "123")); + + QUser user = QUser.user; + + Page page = predicateExecutor.findAll(user.firstname.isNotNull(), + PageRequest.of(0, 10, Sort.by(Sort.Direction.ASC, "address.streetName"))); + + assertThat(page.getContent()).containsExactly(dave, carter, oliver); + } + + @Test // DATAJPA-12 + public void findBySpecificationWithSortByQueryDslOrderSpecifierWithQPageRequestAndQSort() { + + QUser user = QUser.user; + + Page page = predicateExecutor.findAll(user.firstname.isNotNull(), + new QPageRequest(0, 10, new QSort(user.firstname.asc()))); + + assertThat(page.getContent()).containsExactly(carter, dave, oliver); + } + + @Test // DATAJPA-12 + public void findBySpecificationWithSortByQueryDslOrderSpecifierWithQPageRequest() { + + QUser user = QUser.user; + + Page page = predicateExecutor.findAll(user.firstname.isNotNull(), new QPageRequest(0, 10, user.firstname.asc())); + + assertThat(page.getContent()).containsExactly(carter, dave, oliver); + } + + @Test // DATAJPA-12 + public void findBySpecificationWithSortByQueryDslOrderSpecifierForAssociationShouldGenerateLeftJoinWithQPageRequest() { + + oliver.setManager(dave); + dave.setManager(carter); + + QUser user = QUser.user; + + Page page = predicateExecutor.findAll(user.firstname.isNotNull(), + new QPageRequest(0, 10, user.manager.firstname.asc())); + + assertThat(page.getContent()).containsExactly(carter, dave, oliver); + } + + @Test // DATAJPA-500, DATAJPA-635 + public void sortByNestedEmbeddedAttribute() { + + carter.setAddress(new Address("U", "Z", "Y", "41")); + dave.setAddress(new Address("U", "A", "Y", "41")); + oliver.setAddress(new Address("G", "D", "X", "42")); + + List users = predicateExecutor.findAll(QUser.user.address.streetName.asc()); + + assertThat(users).hasSize(3).contains(dave, oliver, carter); + } + + @Test // DATAJPA-566, DATAJPA-635 + public void shouldSupportSortByOperatorWithDateExpressions() { + + carter.setDateOfBirth(new LocalDate(2000, 2, 1).toDate()); + dave.setDateOfBirth(new LocalDate(2000, 1, 1).toDate()); + oliver.setDateOfBirth(new LocalDate(2003, 5, 1).toDate()); + + List users = predicateExecutor.findAll(QUser.user.dateOfBirth.yearMonth().asc()); + + assertThat(users).containsExactly(dave, carter, oliver); + } + + @Test // DATAJPA-665 + public void shouldSupportExistsWithPredicate() throws Exception { + + assertThat(predicateExecutor.exists(user.firstname.eq("Dave"))).isEqualTo(true); + assertThat(predicateExecutor.exists(user.firstname.eq("Unknown"))).isEqualTo(false); + assertThat(predicateExecutor.exists((Predicate) null)).isEqualTo(true); + } + + @Test // DATAJPA-679 + public void shouldSupportFindAllWithPredicateAndSort() { + + List users = predicateExecutor.findAll(user.dateOfBirth.isNull(), Sort.by(Direction.ASC, "firstname")); + + assertThat(users).contains(carter, dave, oliver); + } + + @Test // DATAJPA-585 + public void worksWithUnpagedPageable() { + assertThat(predicateExecutor.findAll(user.dateOfBirth.isNull(), Pageable.unpaged()).getContent()).hasSize(3); + } + + @Test // DATAJPA-912 + public void pageableQueryReportsTotalFromResult() { + + Page firstPage = predicateExecutor.findAll(user.dateOfBirth.isNull(), PageRequest.of(0, 10)); + assertThat(firstPage.getContent()).hasSize(3); + assertThat(firstPage.getTotalElements()).isEqualTo(3L); + + Page secondPage = predicateExecutor.findAll(user.dateOfBirth.isNull(), PageRequest.of(1, 2)); + assertThat(secondPage.getContent()).hasSize(1); + assertThat(secondPage.getTotalElements()).isEqualTo(3L); + } + + @Test // DATAJPA-912 + public void pageableQueryReportsTotalFromCount() { + + Page firstPage = predicateExecutor.findAll(user.dateOfBirth.isNull(), PageRequest.of(0, 3)); + assertThat(firstPage.getContent()).hasSize(3); + assertThat(firstPage.getTotalElements()).isEqualTo(3L); + + Page secondPage = predicateExecutor.findAll(user.dateOfBirth.isNull(), PageRequest.of(10, 10)); + assertThat(secondPage.getContent()).hasSize(0); + assertThat(secondPage.getTotalElements()).isEqualTo(3L); + } + + @Test // DATAJPA-1115 + public void findOneWithPredicateReturnsResultCorrectly() { + assertThat(predicateExecutor.findOne(user.eq(dave))).contains(dave); + } + + @Test // DATAJPA-1115 + public void findOneWithPredicateReturnsOptionalEmptyWhenNoDataFound() { + assertThat(predicateExecutor.findOne(user.firstname.eq("batman"))).isNotPresent(); + } + + @Test(expected = IncorrectResultSizeDataAccessException.class) // DATAJPA-1115 + public void findOneWithPredicateThrowsExceptionForNonUniqueResults() { + predicateExecutor.findOne(user.emailAddress.contains("com")); + } +} diff --git a/src/test/java/org/springframework/data/jpa/repository/support/TransactionalRepositoryTests.java b/src/test/java/org/springframework/data/jpa/repository/support/TransactionalRepositoryTests.java index 3ed70b1d5..71a78eeda 100644 --- a/src/test/java/org/springframework/data/jpa/repository/support/TransactionalRepositoryTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/support/TransactionalRepositoryTests.java @@ -32,7 +32,7 @@ import org.springframework.transaction.TransactionException; import org.springframework.transaction.TransactionStatus; /** - * Integration test for transactional behaviour of repository operations. + * Integration test for transactional behaviour of predicateExecutor operations. * * @author Oliver Gierke */