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 */