Implement FluentQuery for Querydsl and Query by Example.
Add support for both QueryByExampleExecutor and QuerydslPredicateExecutor. This is used in SimpleJpaRepository and QuerydslJpaPredicateExecutor, resulting in various test cases proving support by both examples and Querydsl predicates. NOTE: Class-based DTOs are NOT supported yet. See #2294 Original pull request: #2326.
This commit is contained in:
committed by
Mark Paluch
parent
58b73cf308
commit
ea430f34aa
2
pom.xml
2
pom.xml
@@ -25,7 +25,7 @@
|
||||
<hibernate>5.5.3.Final</hibernate>
|
||||
<mysql-connector-java>8.0.23</mysql-connector-java>
|
||||
<postgresql>42.2.19</postgresql>
|
||||
<springdata.commons>2.6.0-SNAPSHOT</springdata.commons>
|
||||
<springdata.commons>2.6.0-2228-SNAPSHOT</springdata.commons>
|
||||
<vavr>0.10.3</vavr>
|
||||
|
||||
<hibernate.groupId>org.hibernate</hibernate.groupId>
|
||||
|
||||
@@ -144,8 +144,8 @@ public class JpaQueryCreator extends AbstractQueryCreator<CriteriaQuery<? extend
|
||||
|
||||
/**
|
||||
* Finalizes the given {@link Predicate} and applies the given sort. Delegates to
|
||||
* {@link #complete(Predicate, Sort, CriteriaQuery, CriteriaBuilder, Root)} and hands it the current {@link CriteriaQuery}
|
||||
* and {@link CriteriaBuilder}.
|
||||
* {@link #complete(Predicate, Sort, CriteriaQuery, CriteriaBuilder, Root)} and hands it the current
|
||||
* {@link CriteriaQuery} and {@link CriteriaBuilder}.
|
||||
*/
|
||||
@Override
|
||||
protected final CriteriaQuery<? extends Object> complete(Predicate predicate, Sort sort) {
|
||||
@@ -271,10 +271,12 @@ public class JpaQueryCreator extends AbstractQueryCreator<CriteriaQuery<? extend
|
||||
return getTypedPath(root, part).isNotNull();
|
||||
case NOT_IN:
|
||||
// cast required for eclipselink workaround, see DATAJPA-433
|
||||
return upperIfIgnoreCase(getTypedPath(root, part)).in((Expression<Collection<?>>) provider.next(part, Collection.class).getExpression()).not();
|
||||
return upperIfIgnoreCase(getTypedPath(root, part))
|
||||
.in((Expression<Collection<?>>) provider.next(part, Collection.class).getExpression()).not();
|
||||
case IN:
|
||||
// cast required for eclipselink workaround, see DATAJPA-433
|
||||
return upperIfIgnoreCase(getTypedPath(root, part)).in((Expression<Collection<?>>) provider.next(part, Collection.class).getExpression());
|
||||
return upperIfIgnoreCase(getTypedPath(root, part))
|
||||
.in((Expression<Collection<?>>) provider.next(part, Collection.class).getExpression());
|
||||
case STARTING_WITH:
|
||||
case ENDING_WITH:
|
||||
case CONTAINING:
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.TypedQuery;
|
||||
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.repository.query.EscapeCharacter;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery;
|
||||
import org.springframework.data.support.PageableExecutionUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Immutable implementation of {@link FetchableFluentQuery} based on Query by {@link Example}. All methods that return a
|
||||
* {@link FetchableFluentQuery} will return a new instance, not the original.
|
||||
*
|
||||
* @param <S> Domain type
|
||||
* @param <R> Result type
|
||||
* @author Greg Turnquist
|
||||
* @since 2.6
|
||||
*/
|
||||
class FetchableFluentQueryByExample<S, R> extends FluentQuerySupport<R> implements FetchableFluentQuery<R> {
|
||||
|
||||
private final Example<S> example;
|
||||
private final Function<Sort, TypedQuery<S>> finder;
|
||||
private final Function<Example<S>, Long> countOperation;
|
||||
private final Function<Example<S>, Boolean> existsOperation;
|
||||
private final EntityManager entityManager;
|
||||
private final EscapeCharacter escapeCharacter;
|
||||
|
||||
public FetchableFluentQueryByExample(Example<S> example, Function<Sort, TypedQuery<S>> finder,
|
||||
Function<Example<S>, Long> countOperation, Function<Example<S>, Boolean> existsOperation,
|
||||
MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> context,
|
||||
EntityManager entityManager, EscapeCharacter escapeCharacter) {
|
||||
this(example, (Class<R>) example.getProbeType(), Sort.unsorted(), null, finder, countOperation, existsOperation,
|
||||
context, entityManager, escapeCharacter);
|
||||
}
|
||||
|
||||
private FetchableFluentQueryByExample(Example<S> example, Class<R> returnType, Sort sort,
|
||||
@Nullable Collection<String> properties, Function<Sort, TypedQuery<S>> finder,
|
||||
Function<Example<S>, Long> countOperation, Function<Example<S>, Boolean> existsOperation,
|
||||
MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> context,
|
||||
EntityManager entityManager, EscapeCharacter escapeCharacter) {
|
||||
|
||||
super(returnType, sort, properties, context);
|
||||
this.example = example;
|
||||
this.finder = finder;
|
||||
this.countOperation = countOperation;
|
||||
this.existsOperation = existsOperation;
|
||||
this.entityManager = entityManager;
|
||||
this.escapeCharacter = escapeCharacter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FetchableFluentQuery<R> sortBy(Sort sort) {
|
||||
|
||||
return new FetchableFluentQueryByExample<S, R>(this.example, this.resultType, this.sort.and(sort), this.properties,
|
||||
this.finder, this.countOperation, this.existsOperation, this.context, this.entityManager, this.escapeCharacter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <NR> FetchableFluentQuery<NR> as(Class<NR> resultType) {
|
||||
|
||||
if (!resultType.isInterface()) {
|
||||
throw new UnsupportedOperationException("Class-based DTOs are not yet supported.");
|
||||
}
|
||||
|
||||
return new FetchableFluentQueryByExample<S, NR>(this.example, resultType, this.sort, this.properties, this.finder,
|
||||
this.countOperation, this.existsOperation, this.context, this.entityManager, this.escapeCharacter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FetchableFluentQuery<R> project(Collection<String> properties) {
|
||||
|
||||
return new FetchableFluentQueryByExample<>(this.example, this.resultType, this.sort, mergeProperties(properties),
|
||||
this.finder, this.countOperation, this.existsOperation, this.context, this.entityManager, this.escapeCharacter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public R oneValue() {
|
||||
|
||||
TypedQuery<S> limitedQuery = this.finder.apply(this.sort);
|
||||
limitedQuery.setMaxResults(2); // Never need more than 2 values
|
||||
|
||||
List<R> results = limitedQuery //
|
||||
.getResultStream() //
|
||||
.map(getConversionFunction(this.example.getProbeType(), this.resultType)) //
|
||||
.collect(Collectors.toList());
|
||||
;
|
||||
|
||||
if (results.size() > 1) {
|
||||
throw new IncorrectResultSizeDataAccessException(1);
|
||||
}
|
||||
|
||||
return results.isEmpty() ? null : results.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public R firstValue() {
|
||||
|
||||
TypedQuery<S> limitedQuery = this.finder.apply(this.sort);
|
||||
limitedQuery.setMaxResults(1); // Never need more than 1 value
|
||||
|
||||
List<R> results = limitedQuery //
|
||||
.getResultStream() //
|
||||
.map(getConversionFunction(this.example.getProbeType(), this.resultType)) //
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return results.isEmpty() ? null : results.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<R> all() {
|
||||
return stream().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<R> page(Pageable pageable) {
|
||||
return pageable.isUnpaged() ? new PageImpl<>(all()) : readPage(pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<R> stream() {
|
||||
|
||||
return this.finder.apply(this.sort) //
|
||||
.getResultStream() //
|
||||
.map(getConversionFunction(this.example.getProbeType(), this.resultType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long count() {
|
||||
return this.countOperation.apply(example);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return this.existsOperation.apply(example);
|
||||
}
|
||||
|
||||
private Page<R> readPage(Pageable pageable) {
|
||||
|
||||
TypedQuery<S> pagedQuery = this.finder.apply(this.sort);
|
||||
|
||||
if (pageable.isPaged()) {
|
||||
pagedQuery.setFirstResult((int) pageable.getOffset());
|
||||
pagedQuery.setMaxResults(pageable.getPageSize());
|
||||
}
|
||||
|
||||
List<R> paginatedResults = pagedQuery.getResultStream() //
|
||||
.map(getConversionFunction(this.example.getProbeType(), this.resultType)) //
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return PageableExecutionUtils.getPage(paginatedResults, pageable, () -> this.countOperation.apply(this.example));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery;
|
||||
import org.springframework.data.support.PageableExecutionUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import com.querydsl.core.types.Predicate;
|
||||
import com.querydsl.jpa.JPQLQuery;
|
||||
|
||||
/**
|
||||
* Immutable implementation of {@link FetchableFluentQuery} based on a Querydsl {@link Predicate}. All methods that
|
||||
* return a {@link FetchableFluentQuery} will return a new instance, not the original.
|
||||
*
|
||||
* @param <S> Domain type
|
||||
* @param <R> Result type
|
||||
* @author Greg Turnquist
|
||||
* @since 2.6
|
||||
*/
|
||||
class FetchableFluentQueryByPredicate<S, R> extends FluentQuerySupport<R> implements FetchableFluentQuery<R> {
|
||||
|
||||
private final Predicate predicate;
|
||||
private final Function<Sort, JPQLQuery<S>> finder;
|
||||
private final BiFunction<Sort, Pageable, JPQLQuery<S>> pagedFinder;
|
||||
private final Function<Predicate, Long> countOperation;
|
||||
private final Function<Predicate, Boolean> existsOperation;
|
||||
private final Class<S> entityType;
|
||||
|
||||
public FetchableFluentQueryByPredicate(Predicate predicate, Class<R> resultType, Function<Sort, JPQLQuery<S>> finder,
|
||||
BiFunction<Sort, Pageable, JPQLQuery<S>> pagedFinder, Function<Predicate, Long> countOperation,
|
||||
Function<Predicate, Boolean> existsOperation, Class<S> entityType,
|
||||
MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> context) {
|
||||
this(predicate, resultType, Sort.unsorted(), null, finder, pagedFinder, countOperation, existsOperation, entityType,
|
||||
context);
|
||||
}
|
||||
|
||||
private FetchableFluentQueryByPredicate(Predicate predicate, Class<R> resultType, Sort sort,
|
||||
@Nullable Collection<String> properties, Function<Sort, JPQLQuery<S>> finder,
|
||||
BiFunction<Sort, Pageable, JPQLQuery<S>> pagedFinder, Function<Predicate, Long> countOperation,
|
||||
Function<Predicate, Boolean> existsOperation, Class<S> entityType,
|
||||
MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> context) {
|
||||
|
||||
super(resultType, sort, properties, context);
|
||||
this.predicate = predicate;
|
||||
this.finder = finder;
|
||||
this.pagedFinder = pagedFinder;
|
||||
this.countOperation = countOperation;
|
||||
this.existsOperation = existsOperation;
|
||||
this.entityType = entityType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FetchableFluentQuery<R> sortBy(Sort sort) {
|
||||
|
||||
return new FetchableFluentQueryByPredicate<>(this.predicate, this.resultType, this.sort.and(sort), this.properties,
|
||||
this.finder, this.pagedFinder, this.countOperation, this.existsOperation, this.entityType, this.context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <NR> FetchableFluentQuery<NR> as(Class<NR> resultType) {
|
||||
|
||||
if (!resultType.isInterface()) {
|
||||
throw new UnsupportedOperationException("Class-based DTOs are not yet supported.");
|
||||
}
|
||||
|
||||
return new FetchableFluentQueryByPredicate<>(this.predicate, resultType, this.sort, this.properties, this.finder,
|
||||
this.pagedFinder, this.countOperation, this.existsOperation, this.entityType, this.context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FetchableFluentQuery<R> project(Collection<String> properties) {
|
||||
|
||||
return new FetchableFluentQueryByPredicate<>(this.predicate, this.resultType, this.sort,
|
||||
mergeProperties(properties), this.finder, this.pagedFinder, this.countOperation, this.existsOperation,
|
||||
this.entityType, this.context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public R oneValue() {
|
||||
|
||||
List<R> results = this.finder.apply(this.sort) //
|
||||
.limit(2) // Never need more than 2 values
|
||||
.stream() //
|
||||
.map(getConversionFunction(this.entityType, this.resultType)) //
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (results.size() > 1) {
|
||||
throw new IncorrectResultSizeDataAccessException(1);
|
||||
}
|
||||
|
||||
return results.isEmpty() ? null : results.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public R firstValue() {
|
||||
|
||||
List<R> results = this.finder.apply(this.sort) //
|
||||
.limit(1) // Never need more than 1 value
|
||||
.stream() //
|
||||
.map(getConversionFunction(this.entityType, this.resultType)) //
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return results.isEmpty() ? null : results.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<R> all() {
|
||||
return stream().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<R> page(Pageable pageable) {
|
||||
return pageable.isUnpaged() ? new PageImpl<>(all()) : readPage(pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<R> stream() {
|
||||
|
||||
return this.finder.apply(this.sort) //
|
||||
.stream() //
|
||||
.map(getConversionFunction(this.entityType, this.resultType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long count() {
|
||||
return this.countOperation.apply(this.predicate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return this.existsOperation.apply(this.predicate);
|
||||
}
|
||||
|
||||
private Page<R> readPage(Pageable pageable) {
|
||||
|
||||
JPQLQuery<S> pagedQuery = this.pagedFinder.apply(this.sort, pageable);
|
||||
|
||||
List<R> paginatedResults = pagedQuery.stream() //
|
||||
.map(getConversionFunction(this.entityType, this.resultType)) //
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return PageableExecutionUtils.getPage(paginatedResults, pageable, () -> this.countOperation.apply(this.predicate));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Supporting class containing some state and convenience methods for building and executing fluent queries.
|
||||
*
|
||||
* @param <R> The resulting type of the query.
|
||||
* @author Greg Turnquist
|
||||
* @since 2.6
|
||||
*/
|
||||
abstract class FluentQuerySupport<R> {
|
||||
|
||||
protected final Class<R> resultType;
|
||||
protected final Sort sort;
|
||||
protected final @Nullable Set<String> properties;
|
||||
protected final MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> context;
|
||||
|
||||
private final SpelAwareProxyProjectionFactory projectionFactory = new SpelAwareProxyProjectionFactory();
|
||||
|
||||
FluentQuerySupport(Class<R> resultType, Sort sort, @Nullable Collection<String> properties,
|
||||
MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> context) {
|
||||
|
||||
this.resultType = resultType;
|
||||
this.sort = sort;
|
||||
|
||||
if (properties != null) {
|
||||
this.properties = new HashSet<>(properties);
|
||||
} else {
|
||||
this.properties = null;
|
||||
}
|
||||
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
final Collection<String> mergeProperties(Collection<String> additionalProperties) {
|
||||
|
||||
Set<String> newProperties = new HashSet<>();
|
||||
if (this.properties != null) {
|
||||
newProperties.addAll(this.properties);
|
||||
}
|
||||
newProperties.addAll(additionalProperties);
|
||||
return Collections.unmodifiableCollection(newProperties);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
final <S> Function<Object, R> getConversionFunction(Class<S> inputType, Class<R> targetType) {
|
||||
|
||||
if (targetType.isAssignableFrom(inputType)) {
|
||||
return (Function<Object, R>) Function.identity();
|
||||
}
|
||||
|
||||
if (targetType.isInterface()) {
|
||||
return o -> projectionFactory.createProjection(targetType, o);
|
||||
}
|
||||
|
||||
return o -> DefaultConversionService.getSharedInstance().convert(o, targetType);
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,11 @@
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.support;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.LockModeType;
|
||||
@@ -25,10 +28,12 @@ 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.mapping.JpaMetamodelMappingContext;
|
||||
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.repository.query.FluentQuery.FetchableFluentQuery;
|
||||
import org.springframework.data.repository.support.PageableExecutionUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -51,6 +56,7 @@ import com.querydsl.jpa.impl.AbstractJPAQuery;
|
||||
* @author Jocelyn Ntakpe
|
||||
* @author Christoph Strobl
|
||||
* @author Jens Schauder
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public class QuerydslJpaPredicateExecutor<T> implements QuerydslPredicateExecutor<T> {
|
||||
|
||||
@@ -80,9 +86,9 @@ public class QuerydslJpaPredicateExecutor<T> implements QuerydslPredicateExecuto
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.querydsl.QuerydslPredicateExecutor#findOne(com.mysema.query.types.Predicate)
|
||||
*/
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.querydsl.QuerydslPredicateExecutor#findOne(com.mysema.query.types.Predicate)
|
||||
*/
|
||||
@Override
|
||||
public Optional<T> findOne(Predicate predicate) {
|
||||
|
||||
@@ -161,10 +167,45 @@ public class QuerydslJpaPredicateExecutor<T> implements QuerydslPredicateExecuto
|
||||
return PageableExecutionUtils.getPage(query.fetch(), pageable, countQuery::fetchCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends T, R> R findBy(Predicate predicate, Function<FetchableFluentQuery<S>, R> queryFunction) {
|
||||
|
||||
Assert.notNull(predicate, "Predicate must not be null!");
|
||||
Assert.notNull(queryFunction, "Function must not be null!");
|
||||
|
||||
Function<Sort, JPQLQuery<T>> finder = sort -> {
|
||||
JPQLQuery<T> select = createQuery(predicate).select(path);
|
||||
|
||||
if (sort != null) {
|
||||
select = querydsl.applySorting(sort, select);
|
||||
}
|
||||
|
||||
return select;
|
||||
};
|
||||
|
||||
BiFunction<Sort, Pageable, JPQLQuery<T>> pagedFinder = (sort, pageable) -> {
|
||||
|
||||
JPQLQuery<T> select = finder.apply(sort);
|
||||
|
||||
if (pageable.isPaged()) {
|
||||
select = querydsl.applyPagination(pageable, select);
|
||||
}
|
||||
|
||||
return select;
|
||||
};
|
||||
|
||||
FetchableFluentQuery<S> fluentQuery = (FetchableFluentQuery<S>) new FetchableFluentQueryByPredicate<>(predicate,
|
||||
entityInformation.getJavaType(), finder, pagedFinder, this::count, this::exists,
|
||||
this.entityInformation.getJavaType(),
|
||||
new JpaMetamodelMappingContext(Collections.singleton(this.entityManager.getMetamodel())));
|
||||
|
||||
return queryFunction.apply(fluentQuery);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.querydsl.QueryDslPredicateExecutor#count(com.mysema.query.types.Predicate)
|
||||
*/
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.querydsl.QueryDslPredicateExecutor#count(com.mysema.query.types.Predicate)
|
||||
*/
|
||||
@Override
|
||||
public long count(Predicate predicate) {
|
||||
return createQuery(predicate).fetchCount();
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.data.jpa.repository.support;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.LockModeType;
|
||||
@@ -30,6 +31,7 @@ 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.query.FluentQuery;
|
||||
import org.springframework.data.repository.support.PageableExecutionUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -46,13 +48,14 @@ 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
|
||||
* @author Greg Turnquist
|
||||
* @deprecated Instead of this class use {@link QuerydslJpaPredicateExecutor}
|
||||
*/
|
||||
@Deprecated
|
||||
public class QuerydslJpaRepository<T, ID extends Serializable> extends SimpleJpaRepository<T, ID>
|
||||
@@ -164,6 +167,13 @@ public class QuerydslJpaRepository<T, ID extends Serializable> extends SimpleJpa
|
||||
return PageableExecutionUtils.getPage(query.fetch(), pageable, countQuery::fetchCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends T, R> R findBy(Predicate predicate,
|
||||
Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Fluent Query API support for Querydsl is only found in QuerydslJpaPredicateExecutor.");
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.querydsl.QueryDslPredicateExecutor#count(com.mysema.query.types.Predicate)
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.LockModeType;
|
||||
@@ -47,11 +48,16 @@ import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.convert.QueryByExamplePredicateBuilder;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext;
|
||||
import org.springframework.data.jpa.provider.PersistenceProvider;
|
||||
import org.springframework.data.jpa.repository.EntityGraph;
|
||||
import org.springframework.data.jpa.repository.query.EscapeCharacter;
|
||||
import org.springframework.data.jpa.repository.query.QueryUtils;
|
||||
import org.springframework.data.jpa.repository.support.QueryHints.NoHints;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery;
|
||||
import org.springframework.data.repository.support.PageableExecutionUtils;
|
||||
import org.springframework.data.util.ProxyUtils;
|
||||
import org.springframework.data.util.Streamable;
|
||||
@@ -64,6 +70,8 @@ import org.springframework.util.Assert;
|
||||
* Default implementation of the {@link org.springframework.data.repository.CrudRepository} interface. This will offer
|
||||
* you a more sophisticated interface than the plain {@link EntityManager} .
|
||||
*
|
||||
* @param <T> the type of the entity to handle
|
||||
* @param <ID> the type of the entity's identifier
|
||||
* @author Oliver Gierke
|
||||
* @author Eberhard Wolff
|
||||
* @author Thomas Darimont
|
||||
@@ -75,8 +83,7 @@ import org.springframework.util.Assert;
|
||||
* @author Moritz Becker
|
||||
* @author Sander Krabbenborg
|
||||
* @author Jesse Wouters
|
||||
* @param <T> the type of the entity to handle
|
||||
* @param <ID> the type of the entity's identifier
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
@Repository
|
||||
@Transactional(readOnly = true)
|
||||
@@ -87,6 +94,7 @@ public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T
|
||||
private final JpaEntityInformation<T, ?> entityInformation;
|
||||
private final EntityManager em;
|
||||
private final PersistenceProvider provider;
|
||||
private final MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> context;
|
||||
|
||||
private @Nullable CrudMethodMetadata metadata;
|
||||
private EscapeCharacter escapeCharacter = EscapeCharacter.DEFAULT;
|
||||
@@ -105,6 +113,9 @@ public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T
|
||||
this.entityInformation = entityInformation;
|
||||
this.em = entityManager;
|
||||
this.provider = PersistenceProvider.fromEntityManager(entityManager);
|
||||
this.context = em.getMetamodel() != null //
|
||||
? new JpaMetamodelMappingContext(Collections.singleton(em.getMetamodel())) //
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -567,9 +578,30 @@ public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.CrudRepository#count()
|
||||
* @see org.springframework.data.repository.query.QueryByExampleExecutor#findBy(org.springframework.data.domain.Example, java.util.function.Function)
|
||||
*/
|
||||
@Override
|
||||
public <S extends T, R> R findBy(Example<S> example, Function<FetchableFluentQuery<S>, R> queryFunction) {
|
||||
|
||||
Function<Sort, TypedQuery<S>> finder = sort -> {
|
||||
|
||||
ExampleSpecification<S> spec = new ExampleSpecification<>(example, escapeCharacter);
|
||||
Class<S> probeType = example.getProbeType();
|
||||
|
||||
return getQuery(spec, probeType, sort);
|
||||
};
|
||||
|
||||
FetchableFluentQuery<S> fluentQuery = new FetchableFluentQueryByExample<>(example, finder, this::count,
|
||||
this::exists, this.context, this.em, this.escapeCharacter);
|
||||
|
||||
return queryFunction.apply(fluentQuery);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.CrudRepository#count()
|
||||
*/
|
||||
@Override
|
||||
public long count() {
|
||||
return em.createQuery(getCountQueryString(), Long.class).getSingleResult();
|
||||
}
|
||||
@@ -872,8 +904,8 @@ public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T
|
||||
* {@link SimpleJpaRepository#findAllById(Iterable)}. Workaround for OpenJPA not binding collections to in-clauses
|
||||
* correctly when using by-name binding.
|
||||
*
|
||||
* @see <a href="https://issues.apache.org/jira/browse/OPENJPA-2018?focusedCommentId=13924055">OPENJPA-2018</a>
|
||||
* @author Oliver Gierke
|
||||
* @see <a href="https://issues.apache.org/jira/browse/OPENJPA-2018?focusedCommentId=13924055">OPENJPA-2018</a>
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static final class ByIdsSpecification<T> implements Specification<T> {
|
||||
@@ -905,9 +937,9 @@ public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T
|
||||
* {@link Specification} that gives access to the {@link Predicate} instance representing the values contained in the
|
||||
* {@link Example}.
|
||||
*
|
||||
* @param <T>
|
||||
* @author Christoph Strobl
|
||||
* @since 1.10
|
||||
* @param <T>
|
||||
*/
|
||||
private static class ExampleSpecification<T> implements Specification<T> {
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ import static org.springframework.data.jpa.domain.Specification.*;
|
||||
import static org.springframework.data.jpa.domain.Specification.not;
|
||||
import static org.springframework.data.jpa.domain.sample.UserSpecifications.*;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -47,7 +49,6 @@ import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
@@ -75,7 +76,6 @@ import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
||||
/**
|
||||
* Base integration test class for {@code UserRepository}. Loads a basic (non-namespace) Spring configuration file as
|
||||
* well as Hibernate configuration to execute tests.
|
||||
@@ -92,6 +92,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
* @author Andrey Kovalev
|
||||
* @author Sander Krabbenborg
|
||||
* @author Jesse Wouters
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration("classpath:application-context.xml")
|
||||
@@ -2030,6 +2031,186 @@ public class UserRepositoryTests {
|
||||
assertThat(repository.findOne(example)).contains(firstUser);
|
||||
}
|
||||
|
||||
@Test // GH-2294
|
||||
void findByFluentExampleWithSorting() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
User prototype = new User();
|
||||
prototype.setFirstname("v");
|
||||
|
||||
List<User> users = repository.findBy(
|
||||
of(prototype,
|
||||
matching().withIgnorePaths("age", "createdAt", "active").withMatcher("firstname",
|
||||
GenericPropertyMatcher::contains)), //
|
||||
q -> q.sortBy(Sort.by("firstname")).all());
|
||||
|
||||
assertThat(users).containsExactly(thirdUser, firstUser, fourthUser);
|
||||
}
|
||||
|
||||
@Test // GH-2294
|
||||
void findByFluentExampleFirstValue() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
User prototype = new User();
|
||||
prototype.setFirstname("v");
|
||||
|
||||
User firstUser = repository.findBy(
|
||||
of(prototype,
|
||||
matching().withIgnorePaths("age", "createdAt", "active").withMatcher("firstname",
|
||||
GenericPropertyMatcher::contains)), //
|
||||
q -> q.sortBy(Sort.by("firstname")).firstValue());
|
||||
|
||||
assertThat(firstUser).isEqualTo(thirdUser);
|
||||
}
|
||||
|
||||
@Test // GH-2294
|
||||
void findByFluentExampleOneValue() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
User prototype = new User();
|
||||
prototype.setFirstname("v");
|
||||
|
||||
assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class).isThrownBy(() -> {
|
||||
repository.findBy(
|
||||
of(prototype,
|
||||
matching().withIgnorePaths("age", "createdAt", "active").withMatcher("firstname",
|
||||
GenericPropertyMatcher::contains)), //
|
||||
q -> q.sortBy(Sort.by("firstname")).oneValue());
|
||||
});
|
||||
}
|
||||
|
||||
@Test // GH-2294
|
||||
void findByFluentExampleStream() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
User prototype = new User();
|
||||
prototype.setFirstname("v");
|
||||
|
||||
Stream<User> userStream = repository.findBy(
|
||||
of(prototype,
|
||||
matching().withIgnorePaths("age", "createdAt", "active").withMatcher("firstname",
|
||||
GenericPropertyMatcher::contains)), //
|
||||
q -> q.sortBy(Sort.by("firstname")).stream());
|
||||
|
||||
assertThat(userStream).containsExactly(thirdUser, firstUser, fourthUser);
|
||||
}
|
||||
|
||||
@Test // GH-2294
|
||||
void findByFluentExamplePage() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
User prototype = new User();
|
||||
prototype.setFirstname("v");
|
||||
|
||||
Example<User> userProbe = of(prototype, matching().withIgnorePaths("age", "createdAt", "active")
|
||||
.withMatcher("firstname", GenericPropertyMatcher::contains));
|
||||
|
||||
Page<User> page0 = repository.findBy(userProbe, //
|
||||
q -> q.sortBy(Sort.by("firstname")).page(PageRequest.of(0, 2)));
|
||||
|
||||
Page<User> page1 = repository.findBy(userProbe, //
|
||||
q -> q.sortBy(Sort.by("firstname")).page(PageRequest.of(1, 2)));
|
||||
|
||||
assertThat(page0.getContent()).containsExactly(thirdUser, firstUser);
|
||||
assertThat(page1.getContent()).containsExactly(fourthUser);
|
||||
}
|
||||
|
||||
@Test // GH-2294
|
||||
void findByFluentExampleWithInterfaceBasedProjection() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
User prototype = new User();
|
||||
prototype.setFirstname("v");
|
||||
|
||||
List<UserProjectionInterfaceBased> users = repository.findBy(
|
||||
of(prototype,
|
||||
matching().withIgnorePaths("age", "createdAt", "active").withMatcher("firstname",
|
||||
GenericPropertyMatcher::contains)), //
|
||||
q -> q.as(UserProjectionInterfaceBased.class).all());
|
||||
|
||||
assertThat(users).extracting(UserProjectionInterfaceBased::getFirstname)
|
||||
.containsExactlyInAnyOrder(firstUser.getFirstname(), thirdUser.getFirstname(), fourthUser.getFirstname());
|
||||
}
|
||||
|
||||
@Test // GH-2294
|
||||
void findByFluentExampleWithSortedInterfaceBasedProjection() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
User prototype = new User();
|
||||
prototype.setFirstname("v");
|
||||
|
||||
List<UserProjectionInterfaceBased> users = repository.findBy(
|
||||
of(prototype,
|
||||
matching().withIgnorePaths("age", "createdAt", "active").withMatcher("firstname",
|
||||
GenericPropertyMatcher::contains)), //
|
||||
q -> q.as(UserProjectionInterfaceBased.class).sortBy(Sort.by("firstname")).all());
|
||||
|
||||
assertThat(users).extracting(UserProjectionInterfaceBased::getFirstname)
|
||||
.containsExactlyInAnyOrder(thirdUser.getFirstname(), firstUser.getFirstname(), fourthUser.getFirstname());
|
||||
}
|
||||
|
||||
@Test // GH-2294
|
||||
void fluentExamplesWithClassBasedDtosNotYetSupported() {
|
||||
|
||||
@Data
|
||||
class UserDto {
|
||||
String firstname;
|
||||
}
|
||||
|
||||
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> {
|
||||
|
||||
User prototype = new User();
|
||||
prototype.setFirstname("v");
|
||||
|
||||
repository.findBy(
|
||||
of(prototype,
|
||||
matching().withIgnorePaths("age", "createdAt", "active").withMatcher("firstname",
|
||||
GenericPropertyMatcher::contains)), //
|
||||
q -> q.as(UserDto.class).sortBy(Sort.by("firstname")).all());
|
||||
});
|
||||
}
|
||||
|
||||
@Test // GH-2294
|
||||
void countByFluentExample() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
User prototype = new User();
|
||||
prototype.setFirstname("v");
|
||||
|
||||
long numOfUsers = repository.findBy(
|
||||
of(prototype,
|
||||
matching().withIgnorePaths("age", "createdAt", "active").withMatcher("firstname",
|
||||
GenericPropertyMatcher::contains)), //
|
||||
q -> q.sortBy(Sort.by("firstname")).count());
|
||||
|
||||
assertThat(numOfUsers).isEqualTo(3);
|
||||
}
|
||||
|
||||
@Test // GH-2294
|
||||
void existsByFluentExample() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
User prototype = new User();
|
||||
prototype.setFirstname("v");
|
||||
|
||||
boolean exists = repository.findBy(
|
||||
of(prototype,
|
||||
matching().withIgnorePaths("age", "createdAt", "active").withMatcher("firstname",
|
||||
GenericPropertyMatcher::contains)), //
|
||||
q -> q.sortBy(Sort.by("firstname")).exists());
|
||||
|
||||
assertThat(exists).isTrue();
|
||||
}
|
||||
|
||||
@Test // DATAJPA-218
|
||||
void countByExampleWithExcludedAttributes() {
|
||||
|
||||
@@ -2349,4 +2530,8 @@ public class UserRepositoryTests {
|
||||
assertThat(result.getTotalElements()).isEqualTo(2L);
|
||||
return result;
|
||||
}
|
||||
|
||||
private interface UserProjectionInterfaceBased {
|
||||
String getFirstname();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,17 +17,19 @@ package org.springframework.data.jpa.repository.support;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.sql.Date;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
@@ -60,6 +62,7 @@ import com.querydsl.core.types.dsl.PathBuilderFactory;
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @author Malte Mauelshagen
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration({ "classpath:infrastructure.xml" })
|
||||
@@ -87,7 +90,8 @@ class QuerydslJpaPredicateExecutorUnitTests {
|
||||
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);
|
||||
this.predicateExecutor = new QuerydslJpaPredicateExecutor<>(information, em, SimpleEntityPathResolver.INSTANCE,
|
||||
null);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -217,7 +221,8 @@ class QuerydslJpaPredicateExecutorUnitTests {
|
||||
|
||||
QUser user = QUser.user;
|
||||
|
||||
Page<User> page = predicateExecutor.findAll(user.firstname.isNotNull(), new QPageRequest(0, 10, user.firstname.asc()));
|
||||
Page<User> page = predicateExecutor.findAll(user.firstname.isNotNull(),
|
||||
new QPageRequest(0, 10, user.firstname.asc()));
|
||||
|
||||
assertThat(page.getContent()).containsExactly(carter, dave, oliver);
|
||||
}
|
||||
@@ -317,7 +322,127 @@ class QuerydslJpaPredicateExecutorUnitTests {
|
||||
|
||||
@Test // DATAJPA-1115
|
||||
void findOneWithPredicateThrowsExceptionForNonUniqueResults() {
|
||||
|
||||
assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class)
|
||||
.isThrownBy(() -> predicateExecutor.findOne(user.emailAddress.contains("com")));
|
||||
}
|
||||
|
||||
@Test // GH-2294
|
||||
void findByFluentPredicate() {
|
||||
|
||||
List<User> users = predicateExecutor.findBy(user.firstname.eq("Dave"), q -> q.sortBy(Sort.by("firstname")).all());
|
||||
|
||||
assertThat(users).containsExactly(dave);
|
||||
}
|
||||
|
||||
@Test // GH-2294
|
||||
void findByFluentPredicateWithSorting() {
|
||||
|
||||
List<User> users = predicateExecutor.findBy(user.firstname.isNotNull(), q -> q.sortBy(Sort.by("firstname")).all());
|
||||
|
||||
assertThat(users).containsExactly(carter, dave, oliver);
|
||||
}
|
||||
|
||||
@Test // GH-2294
|
||||
void findByFluentPredicateWithEqualsAndSorting() {
|
||||
|
||||
List<User> users = predicateExecutor.findBy(user.firstname.contains("v"),
|
||||
q -> q.sortBy(Sort.by("firstname")).all());
|
||||
|
||||
assertThat(users).containsExactly(dave, oliver);
|
||||
}
|
||||
|
||||
@Test // GH-2294
|
||||
void findByFluentPredicateFirstValue() {
|
||||
|
||||
User firstUser = predicateExecutor.findBy(user.firstname.contains("v"),
|
||||
q -> q.sortBy(Sort.by("firstname")).firstValue());
|
||||
|
||||
assertThat(firstUser).isEqualTo(dave);
|
||||
}
|
||||
|
||||
@Test // GH-2294
|
||||
void findByFluentPredicateOneValue() {
|
||||
|
||||
assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class).isThrownBy(
|
||||
() -> predicateExecutor.findBy(user.firstname.contains("v"), q -> q.sortBy(Sort.by("firstname")).oneValue()));
|
||||
}
|
||||
|
||||
@Test // GH-2294
|
||||
void findByFluentPredicateStream() {
|
||||
|
||||
Stream<User> userStream = predicateExecutor.findBy(user.firstname.contains("v"),
|
||||
q -> q.sortBy(Sort.by("firstname")).stream());
|
||||
|
||||
assertThat(userStream).containsExactly(dave, oliver);
|
||||
}
|
||||
|
||||
@Test // GH-2294
|
||||
void findByFluentPredicatePage() {
|
||||
|
||||
Predicate predicate = user.firstname.contains("v");
|
||||
|
||||
Page<User> page0 = predicateExecutor.findBy(predicate,
|
||||
q -> q.sortBy(Sort.by("firstname")).page(PageRequest.of(0, 1)));
|
||||
|
||||
Page<User> page1 = predicateExecutor.findBy(predicate,
|
||||
q -> q.sortBy(Sort.by("firstname")).page(PageRequest.of(1, 1)));
|
||||
|
||||
assertThat(page0.getContent()).containsExactly(dave);
|
||||
assertThat(page1.getContent()).containsExactly(oliver);
|
||||
}
|
||||
|
||||
@Test // GH-2294
|
||||
void findByFluentPredicateWithInterfaceBasedProjection() {
|
||||
|
||||
List<UserProjectionInterfaceBased> users = predicateExecutor.findBy(user.firstname.contains("v"),
|
||||
q -> q.as(UserProjectionInterfaceBased.class).all());
|
||||
|
||||
assertThat(users).extracting(UserProjectionInterfaceBased::getFirstname)
|
||||
.containsExactlyInAnyOrder(dave.getFirstname(), oliver.getFirstname());
|
||||
}
|
||||
|
||||
@Test // GH-2294
|
||||
void findByFluentPredicateWithSortedInterfaceBasedProjection() {
|
||||
|
||||
List<UserProjectionInterfaceBased> userProjections = predicateExecutor.findBy(user.firstname.contains("v"),
|
||||
q -> q.as(UserProjectionInterfaceBased.class).sortBy(Sort.by("firstname")).all());
|
||||
|
||||
assertThat(userProjections).extracting(UserProjectionInterfaceBased::getFirstname)
|
||||
.containsExactly(dave.getFirstname(), oliver.getFirstname());
|
||||
}
|
||||
|
||||
@Test // GH-2294
|
||||
void countByFluentPredicate() {
|
||||
|
||||
long userCount = predicateExecutor.findBy(user.firstname.contains("v"),
|
||||
q -> q.sortBy(Sort.by("firstname")).count());
|
||||
|
||||
assertThat(userCount).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test // GH-2294
|
||||
void existsByFluentPredicate() {
|
||||
|
||||
boolean exists = predicateExecutor.findBy(user.firstname.contains("v"),
|
||||
q -> q.sortBy(Sort.by("firstname")).exists());
|
||||
|
||||
assertThat(exists).isTrue();
|
||||
}
|
||||
|
||||
@Test // GH-2294
|
||||
void fluentExamplesWithClassBasedDtosNotYetSupported() {
|
||||
|
||||
@Data
|
||||
class UserDto {
|
||||
String firstname;
|
||||
}
|
||||
|
||||
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> predicateExecutor
|
||||
.findBy(user.firstname.contains("v"), q -> q.as(UserDto.class).sortBy(Sort.by("firstname")).all()));
|
||||
}
|
||||
|
||||
private interface UserProjectionInterfaceBased {
|
||||
String getFirstname();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,18 +16,21 @@
|
||||
package org.springframework.data.jpa.repository.support;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.springframework.data.domain.Example.*;
|
||||
import static org.springframework.data.domain.ExampleMatcher.*;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.sql.Date;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
@@ -58,6 +61,7 @@ import com.querydsl.core.types.dsl.PathBuilderFactory;
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @author Malte Mauelshagen
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration({ "classpath:infrastructure.xml" })
|
||||
@@ -325,7 +329,15 @@ class QuerydslJpaRepositoryTests {
|
||||
|
||||
@Test // DATAJPA-1115
|
||||
void findOneWithPredicateThrowsExceptionForNonUniqueResults() {
|
||||
|
||||
assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class)
|
||||
.isThrownBy(() -> repository.findOne(user.emailAddress.contains("com")));
|
||||
}
|
||||
|
||||
@Test // GH-2294
|
||||
void findByFluentQuery() {
|
||||
|
||||
assertThatExceptionOfType(UnsupportedOperationException.class)
|
||||
.isThrownBy(() -> repository.findBy(user.firstname.eq("Dave"), q -> q.sortBy(Sort.by("firstname")).all()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.jpa.domain.sample.User;
|
||||
@@ -186,12 +185,11 @@ class SimpleJpaRepositoryUnitTests {
|
||||
newUser.setId(23);
|
||||
|
||||
when(information.isNew(newUser)).thenReturn(false);
|
||||
when(em.find(User.class,23)).thenReturn(null);
|
||||
when(em.find(User.class, 23)).thenReturn(null);
|
||||
|
||||
repo.delete(newUser);
|
||||
|
||||
verify(em, never()).remove(newUser);
|
||||
verify(em, never()).merge(newUser);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user