Browse Source
We now support execution of Querydsl Predicates using reactive MongoDB repositories. Reactive repositories are only required to add ReactiveQuerydslPredicateExecutor to their declaration to enable Querydsl.
public interface PersonRepository extends ReactiveMongoRepository<Person, String>, ReactiveQuerydslPredicateExecutor<Person> {
// additional query methods go here
}
PersonRepository repository = …;
QPerson person = new QPerson("person");
Flux<Person> result = repository.findAll(person.address.zipCode.eq("C0123"));
Original Pull Request: #635
pull/638/head
7 changed files with 899 additions and 4 deletions
@ -0,0 +1,232 @@
@@ -0,0 +1,232 @@
|
||||
/* |
||||
* Copyright 2019 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.mongodb.repository.support; |
||||
|
||||
import reactor.core.publisher.Flux; |
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import java.util.List; |
||||
|
||||
import org.springframework.data.domain.Sort; |
||||
import org.springframework.data.domain.Sort.Order; |
||||
import org.springframework.data.mongodb.core.ReactiveMongoOperations; |
||||
import org.springframework.data.mongodb.repository.query.MongoEntityInformation; |
||||
import org.springframework.data.querydsl.EntityPathResolver; |
||||
import org.springframework.data.querydsl.QSort; |
||||
import org.springframework.data.querydsl.QuerydslPredicateExecutor; |
||||
import org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor; |
||||
import org.springframework.data.querydsl.SimpleEntityPathResolver; |
||||
import org.springframework.data.repository.core.EntityInformation; |
||||
import org.springframework.util.Assert; |
||||
|
||||
import com.querydsl.core.types.EntityPath; |
||||
import com.querydsl.core.types.Expression; |
||||
import com.querydsl.core.types.OrderSpecifier; |
||||
import com.querydsl.core.types.Predicate; |
||||
import com.querydsl.core.types.dsl.PathBuilder; |
||||
|
||||
/** |
||||
* MongoDB-specific {@link QuerydslPredicateExecutor} that allows execution {@link Predicate}s in various forms. |
||||
* |
||||
* @author Mark Paluch |
||||
* @since 2.2 |
||||
*/ |
||||
public class ReactiveQuerydslMongoPredicateExecutor<T> implements ReactiveQuerydslPredicateExecutor<T> { |
||||
|
||||
private final PathBuilder<T> builder; |
||||
private final EntityInformation<T, ?> entityInformation; |
||||
private final ReactiveMongoOperations mongoOperations; |
||||
|
||||
/** |
||||
* Creates a new {@link ReactiveQuerydslMongoPredicateExecutor} for the given {@link MongoEntityInformation} and |
||||
* {@link ReactiveMongoOperations}. Uses the {@link SimpleEntityPathResolver} to create an {@link EntityPath} for the |
||||
* given domain class. |
||||
* |
||||
* @param entityInformation must not be {@literal null}. |
||||
* @param mongoOperations must not be {@literal null}. |
||||
*/ |
||||
public ReactiveQuerydslMongoPredicateExecutor(MongoEntityInformation<T, ?> entityInformation, |
||||
ReactiveMongoOperations mongoOperations) { |
||||
this(entityInformation, mongoOperations, SimpleEntityPathResolver.INSTANCE); |
||||
} |
||||
|
||||
/** |
||||
* Creates a new {@link ReactiveQuerydslMongoPredicateExecutor} for the given {@link MongoEntityInformation}, |
||||
* {@link ReactiveMongoOperations} and {@link EntityPathResolver}. |
||||
* |
||||
* @param entityInformation must not be {@literal null}. |
||||
* @param mongoOperations must not be {@literal null}. |
||||
* @param resolver must not be {@literal null}. |
||||
*/ |
||||
public ReactiveQuerydslMongoPredicateExecutor(MongoEntityInformation<T, ?> entityInformation, |
||||
ReactiveMongoOperations mongoOperations, EntityPathResolver resolver) { |
||||
|
||||
Assert.notNull(resolver, "EntityPathResolver must not be null!"); |
||||
|
||||
EntityPath<T> path = resolver.createPath(entityInformation.getJavaType()); |
||||
|
||||
this.builder = new PathBuilder<T>(path.getType(), path.getMetadata()); |
||||
this.entityInformation = entityInformation; |
||||
this.mongoOperations = mongoOperations; |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor#findOne(com.querydsl.core.types.Predicate) |
||||
*/ |
||||
@Override |
||||
public Mono<T> findOne(Predicate predicate) { |
||||
|
||||
Assert.notNull(predicate, "Predicate must not be null!"); |
||||
|
||||
return createQueryFor(predicate).fetchOne(); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor#findAll(com.querydsl.core.types.Predicate) |
||||
*/ |
||||
@Override |
||||
public Flux<T> findAll(Predicate predicate) { |
||||
|
||||
Assert.notNull(predicate, "Predicate must not be null!"); |
||||
|
||||
return createQueryFor(predicate).fetch(); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor#findAll(com.querydsl.core.types.Predicate, com.querydsl.core.types.OrderSpecifier[]) |
||||
*/ |
||||
@Override |
||||
public Flux<T> findAll(Predicate predicate, OrderSpecifier<?>... orders) { |
||||
|
||||
Assert.notNull(predicate, "Predicate must not be null!"); |
||||
Assert.notNull(orders, "Order specifiers must not be null!"); |
||||
|
||||
return createQueryFor(predicate).orderBy(orders).fetch(); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor#findAll(com.querydsl.core.types.Predicate, org.springframework.data.domain.Sort) |
||||
*/ |
||||
@Override |
||||
public Flux<T> findAll(Predicate predicate, Sort sort) { |
||||
|
||||
Assert.notNull(predicate, "Predicate must not be null!"); |
||||
Assert.notNull(sort, "Sort must not be null!"); |
||||
|
||||
return applySorting(createQueryFor(predicate), sort).fetch(); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor#findAll(com.querydsl.core.types.OrderSpecifier[]) |
||||
*/ |
||||
@Override |
||||
public Flux<T> findAll(OrderSpecifier<?>... orders) { |
||||
|
||||
Assert.notNull(orders, "Order specifiers must not be null!"); |
||||
|
||||
return createQuery().orderBy(orders).fetch(); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor#count(com.querydsl.core.types.Predicate) |
||||
*/ |
||||
@Override |
||||
public Mono<Long> count(Predicate predicate) { |
||||
|
||||
Assert.notNull(predicate, "Predicate must not be null!"); |
||||
|
||||
return createQueryFor(predicate).fetchCount(); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor#exists(com.querydsl.core.types.Predicate) |
||||
*/ |
||||
@Override |
||||
public Mono<Boolean> exists(Predicate predicate) { |
||||
|
||||
Assert.notNull(predicate, "Predicate must not be null!"); |
||||
|
||||
return createQueryFor(predicate).fetchCount().map(it -> it != 0); |
||||
} |
||||
|
||||
/** |
||||
* Creates a {@link ReactiveSpringDataMongodbQuery} for the given {@link Predicate}. |
||||
* |
||||
* @param predicate |
||||
* @return |
||||
*/ |
||||
private ReactiveSpringDataMongodbQuery<T> createQueryFor(Predicate predicate) { |
||||
return createQuery().where(predicate); |
||||
} |
||||
|
||||
/** |
||||
* Creates a {@link ReactiveSpringDataMongodbQuery}. |
||||
* |
||||
* @return |
||||
*/ |
||||
private ReactiveSpringDataMongodbQuery<T> createQuery() { |
||||
SpringDataMongodbSerializer serializer = new SpringDataMongodbSerializer(mongoOperations.getConverter()); |
||||
|
||||
Class<T> javaType = entityInformation.getJavaType(); |
||||
return new ReactiveSpringDataMongodbQuery<>(serializer, mongoOperations, javaType, |
||||
mongoOperations.getCollectionName(javaType)); |
||||
} |
||||
|
||||
/** |
||||
* Applies the given {@link Sort} to the given {@link ReactiveSpringDataMongodbQuery}. |
||||
* |
||||
* @param query |
||||
* @param sort |
||||
* @return |
||||
*/ |
||||
private ReactiveSpringDataMongodbQuery<T> applySorting(ReactiveSpringDataMongodbQuery<T> query, Sort sort) { |
||||
|
||||
// TODO: find better solution than instanceof check
|
||||
if (sort instanceof QSort) { |
||||
|
||||
List<OrderSpecifier<?>> orderSpecifiers = ((QSort) sort).getOrderSpecifiers(); |
||||
query.orderBy(orderSpecifiers.toArray(new OrderSpecifier<?>[orderSpecifiers.size()])); |
||||
|
||||
return query; |
||||
} |
||||
|
||||
sort.stream().map(this::toOrder).forEach(query::orderBy); |
||||
|
||||
return query; |
||||
} |
||||
|
||||
/** |
||||
* Transforms a plain {@link Order} into a Querydsl specific {@link OrderSpecifier}. |
||||
* |
||||
* @param order |
||||
* @return |
||||
*/ |
||||
@SuppressWarnings({ "rawtypes", "unchecked" }) |
||||
private OrderSpecifier<?> toOrder(Order order) { |
||||
|
||||
Expression<Object> property = builder.get(order.getProperty()); |
||||
|
||||
return new OrderSpecifier( |
||||
order.isAscending() ? com.querydsl.core.types.Order.ASC : com.querydsl.core.types.Order.DESC, property); |
||||
} |
||||
} |
||||
@ -0,0 +1,282 @@
@@ -0,0 +1,282 @@
|
||||
/* |
||||
* Copyright 2019 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.mongodb.repository.support; |
||||
|
||||
import reactor.core.publisher.Flux; |
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import java.util.Collection; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
|
||||
import org.springframework.data.mongodb.core.ReactiveFindOperation.FindWithProjection; |
||||
import org.springframework.data.mongodb.core.ReactiveMongoOperations; |
||||
import org.springframework.data.mongodb.core.query.BasicQuery; |
||||
import org.springframework.data.mongodb.core.query.Query; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.util.LinkedMultiValueMap; |
||||
import org.springframework.util.MultiValueMap; |
||||
|
||||
import com.querydsl.core.JoinExpression; |
||||
import com.querydsl.core.QueryMetadata; |
||||
import com.querydsl.core.QueryModifiers; |
||||
import com.querydsl.core.types.Expression; |
||||
import com.querydsl.core.types.ExpressionUtils; |
||||
import com.querydsl.core.types.Operation; |
||||
import com.querydsl.core.types.OrderSpecifier; |
||||
import com.querydsl.core.types.Path; |
||||
import com.querydsl.core.types.Predicate; |
||||
import com.querydsl.core.types.dsl.CollectionPathBase; |
||||
|
||||
/** |
||||
* MongoDB query with utilizing {@link ReactiveMongoOperations} for command execution. |
||||
* |
||||
* @param <K> result type |
||||
* @param <Q> concrete subtype |
||||
* @author Mark Paluch |
||||
* @since 2.2 |
||||
*/ |
||||
class ReactiveSpringDataMongodbQuery<K> extends QuerydslAbstractMongodbQuery<K, ReactiveSpringDataMongodbQuery<K>> { |
||||
|
||||
private final Class<K> entityClass; |
||||
private final ReactiveMongoOperations mongoOperations; |
||||
private final FindWithProjection<K> find; |
||||
|
||||
ReactiveSpringDataMongodbQuery(ReactiveMongoOperations mongoOperations, Class<? extends K> entityClass) { |
||||
|
||||
super(new SpringDataMongodbSerializer(mongoOperations.getConverter())); |
||||
|
||||
this.entityClass = (Class<K>) entityClass; |
||||
this.mongoOperations = mongoOperations; |
||||
this.find = mongoOperations.query(this.entityClass); |
||||
} |
||||
|
||||
ReactiveSpringDataMongodbQuery(MongodbDocumentSerializer serializer, ReactiveMongoOperations mongoOperations, |
||||
Class<? extends K> entityClass, String collection) { |
||||
|
||||
super(serializer); |
||||
|
||||
this.entityClass = (Class<K>) entityClass; |
||||
this.mongoOperations = mongoOperations; |
||||
this.find = mongoOperations.query(this.entityClass).inCollection(collection); |
||||
} |
||||
|
||||
/** |
||||
* Fetch all matching query results. |
||||
* |
||||
* @return {@link Flux} emitting all query results or {@link Flux#empty()} if there are none. |
||||
*/ |
||||
public Flux<K> fetch() { |
||||
return createQuery().flatMapMany(it -> find.matching(it).all()); |
||||
} |
||||
|
||||
/** |
||||
* Fetch the first matching query result. |
||||
* |
||||
* @return {@link Mono} emitting the first query result or {@link Mono#empty()} if there are none. |
||||
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found. |
||||
*/ |
||||
public Mono<K> fetchOne() { |
||||
return createQuery().flatMap(it -> find.matching(it).one()); |
||||
} |
||||
|
||||
/** |
||||
* Fetch the count of matching query results. |
||||
* |
||||
* @return {@link Mono} emitting the first query result count. Emits always a count even item. |
||||
*/ |
||||
public Mono<Long> fetchCount() { |
||||
return createQuery().flatMap(it -> find.matching(it).count()); |
||||
} |
||||
|
||||
/** |
||||
* Define a join. |
||||
* |
||||
* @param ref reference |
||||
* @param target join target |
||||
* @return new instance of {@link QuerydslJoinBuilder}. |
||||
*/ |
||||
public <T> QuerydslJoinBuilder<ReactiveSpringDataMongodbQuery<K>, K, T> join(Path<T> ref, Path<T> target) { |
||||
return new QuerydslJoinBuilder<>(getQueryMixin(), ref, target); |
||||
} |
||||
|
||||
/** |
||||
* Define a join. |
||||
* |
||||
* @param ref reference |
||||
* @param target join target |
||||
* @return new instance of {@link QuerydslJoinBuilder}. |
||||
*/ |
||||
public <T> QuerydslJoinBuilder<ReactiveSpringDataMongodbQuery<K>, K, T> join(CollectionPathBase<?, T, ?> ref, |
||||
Path<T> target) { |
||||
return new QuerydslJoinBuilder<>(getQueryMixin(), ref, target); |
||||
} |
||||
|
||||
/** |
||||
* Define a constraint for an embedded object. |
||||
* |
||||
* @param collection collection must not be {@literal null}. |
||||
* @param target target must not be {@literal null}. |
||||
* @return new instance of {@link QuerydslAnyEmbeddedBuilder}. |
||||
*/ |
||||
public <T> QuerydslAnyEmbeddedBuilder<ReactiveSpringDataMongodbQuery<K>, K> anyEmbedded( |
||||
Path<? extends Collection<T>> collection, Path<T> target) { |
||||
return new QuerydslAnyEmbeddedBuilder<>(getQueryMixin(), collection); |
||||
} |
||||
|
||||
protected Mono<Query> createQuery() { |
||||
|
||||
QueryMetadata metadata = getQueryMixin().getMetadata(); |
||||
|
||||
return createQuery(createFilter(metadata), metadata.getProjection(), metadata.getModifiers(), |
||||
metadata.getOrderBy()); |
||||
} |
||||
|
||||
/** |
||||
* Creates a MongoDB query that is emitted through a {@link Mono} given {@link Mono} of {@link Predicate}. |
||||
* |
||||
* @param filter must not be {@literal null}. |
||||
* @param projection can be {@literal null} if no projection is given. Query requests all fields in such case. |
||||
* @param modifiers must not be {@literal null}. |
||||
* @param orderBy must not be {@literal null}. |
||||
* @return {@link Mono} emitting the {@link Query}. |
||||
*/ |
||||
protected Mono<Query> createQuery(Mono<Predicate> filter, @Nullable Expression<?> projection, |
||||
QueryModifiers modifiers, List<OrderSpecifier<?>> orderBy) { |
||||
|
||||
return filter.map(this::createQuery) //
|
||||
.defaultIfEmpty(createQuery(null)) //
|
||||
.map(it -> { |
||||
|
||||
BasicQuery basicQuery = new BasicQuery(it, createProjection(projection)); |
||||
|
||||
Integer limit = modifiers.getLimitAsInteger(); |
||||
Integer offset = modifiers.getOffsetAsInteger(); |
||||
|
||||
if (limit != null) { |
||||
basicQuery.limit(limit); |
||||
} |
||||
if (offset != null) { |
||||
basicQuery.skip(offset); |
||||
} |
||||
if (orderBy.size() > 0) { |
||||
basicQuery.setSortObject(createSort(orderBy)); |
||||
} |
||||
|
||||
return basicQuery; |
||||
}); |
||||
} |
||||
|
||||
protected Mono<Predicate> createFilter(QueryMetadata metadata) { |
||||
|
||||
if (!metadata.getJoins().isEmpty()) { |
||||
|
||||
return createJoinFilter(metadata).map(it -> ExpressionUtils.allOf(metadata.getWhere(), it)) |
||||
.switchIfEmpty(Mono.justOrEmpty(metadata.getWhere())); |
||||
} |
||||
|
||||
return Mono.justOrEmpty(metadata.getWhere()); |
||||
} |
||||
|
||||
/** |
||||
* Creates a Join filter by querying {@link com.mongodb.DBRef references}. |
||||
* |
||||
* @param metadata |
||||
* @return |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
protected Mono<Predicate> createJoinFilter(QueryMetadata metadata) { |
||||
|
||||
MultiValueMap<Expression<?>, Mono<Predicate>> predicates = new LinkedMultiValueMap<>(); |
||||
List<JoinExpression> joins = metadata.getJoins(); |
||||
|
||||
for (int i = joins.size() - 1; i >= 0; i--) { |
||||
|
||||
JoinExpression join = joins.get(i); |
||||
Path<?> source = (Path) ((Operation<?>) join.getTarget()).getArg(0); |
||||
Path<?> target = (Path) ((Operation<?>) join.getTarget()).getArg(1); |
||||
Collection<Mono<Predicate>> extraFilters = predicates.get(target.getRoot()); |
||||
|
||||
Mono<Predicate> filter = allOf(extraFilters).map(it -> ExpressionUtils.allOf(join.getCondition(), it)) |
||||
.switchIfEmpty(Mono.justOrEmpty(join.getCondition())); |
||||
|
||||
Mono<Predicate> predicate = getIds(target.getType(), filter) //
|
||||
.collectList() //
|
||||
.handle((it, sink) -> { |
||||
|
||||
if (it.isEmpty()) { |
||||
sink.error(new NoMatchException(source)); |
||||
return; |
||||
} |
||||
|
||||
Path<?> path = ExpressionUtils.path(String.class, source, "$id"); |
||||
sink.next(ExpressionUtils.in((Path<Object>) path, it)); |
||||
}); |
||||
|
||||
predicates.add(source.getRoot(), predicate); |
||||
} |
||||
|
||||
Path<?> source = (Path) ((Operation) joins.get(0).getTarget()).getArg(0); |
||||
return allOf(predicates.get(source.getRoot())).onErrorResume(NoMatchException.class, |
||||
e -> Mono.just(ExpressionUtils.predicate(QuerydslMongoOps.NO_MATCH, e.source))); |
||||
} |
||||
|
||||
private Mono<Predicate> allOf(@Nullable Collection<Mono<Predicate>> predicates) { |
||||
return predicates != null ? Flux.concat(predicates).collectList().map(ExpressionUtils::allOf) : Mono.empty(); |
||||
} |
||||
|
||||
/** |
||||
* Fetch the list of ids matching a given condition. |
||||
* |
||||
* @param targetType must not be {@literal null}. |
||||
* @param condition must not be {@literal null}. |
||||
* @return empty {@link List} if none found. |
||||
*/ |
||||
protected Flux<Object> getIds(Class<?> targetType, Mono<Predicate> condition) { |
||||
|
||||
return condition.flatMapMany(it -> getIds(targetType, it)) |
||||
.switchIfEmpty(Flux.defer(() -> getIds(targetType, (Predicate) null))); |
||||
} |
||||
|
||||
/** |
||||
* Fetch the list of ids matching a given condition. |
||||
* |
||||
* @param targetType must not be {@literal null}. |
||||
* @param condition must not be {@literal null}. |
||||
* @return empty {@link List} if none found. |
||||
*/ |
||||
protected Flux<Object> getIds(Class<?> targetType, @Nullable Predicate condition) { |
||||
return createQuery(Mono.justOrEmpty(condition), null, QueryModifiers.EMPTY, Collections.emptyList()) |
||||
.flatMapMany(query -> mongoOperations.findDistinct(query, "_id", targetType, Object.class)); |
||||
} |
||||
|
||||
/** |
||||
* Marker exception to indicate no matches for a query using reference Id's. |
||||
*/ |
||||
static class NoMatchException extends RuntimeException { |
||||
|
||||
final Path<?> source; |
||||
|
||||
public NoMatchException(Path<?> source) { |
||||
this.source = source; |
||||
} |
||||
|
||||
@Override |
||||
public synchronized Throwable fillInStackTrace() { |
||||
return null; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,278 @@
@@ -0,0 +1,278 @@
|
||||
/* |
||||
* Copyright 2019 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.mongodb.repository.support; |
||||
|
||||
import reactor.core.publisher.Flux; |
||||
import reactor.test.StepVerifier; |
||||
|
||||
import java.util.Arrays; |
||||
import java.util.LinkedHashSet; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException; |
||||
import org.springframework.dao.PermissionDeniedDataAccessException; |
||||
import org.springframework.data.domain.Sort; |
||||
import org.springframework.data.domain.Sort.Direction; |
||||
import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory; |
||||
import org.springframework.data.mongodb.core.ReactiveMongoOperations; |
||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate; |
||||
import org.springframework.data.mongodb.repository.Address; |
||||
import org.springframework.data.mongodb.repository.Person; |
||||
import org.springframework.data.mongodb.repository.QAddress; |
||||
import org.springframework.data.mongodb.repository.QPerson; |
||||
import org.springframework.data.mongodb.repository.QUser; |
||||
import org.springframework.data.mongodb.repository.User; |
||||
import org.springframework.data.mongodb.repository.query.MongoEntityInformation; |
||||
import org.springframework.test.context.ContextConfiguration; |
||||
import org.springframework.test.context.junit4.SpringRunner; |
||||
|
||||
import com.mongodb.MongoException; |
||||
import com.mongodb.reactivestreams.client.MongoDatabase; |
||||
|
||||
/** |
||||
* Tests for {@link ReactiveQuerydslMongoPredicateExecutor}. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
@RunWith(SpringRunner.class) |
||||
@ContextConfiguration("classpath:reactive-infrastructure.xml") |
||||
public class ReactiveQuerydslMongoPredicateExecutorIntegrationTests { |
||||
|
||||
@Autowired ReactiveMongoOperations operations; |
||||
@Autowired ReactiveMongoDatabaseFactory dbFactory; |
||||
|
||||
ReactiveQuerydslMongoPredicateExecutor<Person> repository; |
||||
|
||||
Person dave, oliver, carter; |
||||
QPerson person; |
||||
|
||||
@Before |
||||
public void setup() { |
||||
|
||||
ReactiveMongoRepositoryFactory factory = new ReactiveMongoRepositoryFactory(operations); |
||||
MongoEntityInformation<Person, String> entityInformation = factory.getEntityInformation(Person.class); |
||||
repository = new ReactiveQuerydslMongoPredicateExecutor<>(entityInformation, operations); |
||||
|
||||
operations.dropCollection(Person.class) //
|
||||
.as(StepVerifier::create) //
|
||||
.verifyComplete(); |
||||
|
||||
dave = new Person("Dave", "Matthews", 42); |
||||
oliver = new Person("Oliver August", "Matthews", 4); |
||||
carter = new Person("Carter", "Beauford", 49); |
||||
|
||||
person = new QPerson("person"); |
||||
|
||||
operations.insertAll(Arrays.asList(oliver, dave, carter)).as(StepVerifier::create) //
|
||||
.expectNextCount(3) //
|
||||
.verifyComplete(); |
||||
} |
||||
|
||||
@Test // DATAMONGO-2182
|
||||
public void shouldSupportExistsWithPredicate() { |
||||
|
||||
repository.exists(person.firstname.eq("Dave")) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(true) //
|
||||
.verifyComplete(); |
||||
|
||||
repository.exists(person.firstname.eq("Unknown")) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(false) //
|
||||
.verifyComplete(); |
||||
} |
||||
|
||||
@Test // DATAMONGO-2182
|
||||
public void shouldSupportFindAllWithPredicateAndSort() { |
||||
|
||||
repository.findAll(person.lastname.isNotNull(), Sort.by(Direction.ASC, "firstname")) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(carter, dave, oliver) //
|
||||
.verifyComplete(); |
||||
} |
||||
|
||||
@Test // DATAMONGO-2182
|
||||
public void findOneWithPredicateReturnsResultCorrectly() { |
||||
|
||||
repository.findOne(person.firstname.eq(dave.getFirstname())) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(dave) //
|
||||
.verifyComplete(); |
||||
} |
||||
|
||||
@Test // DATAMONGO-2182
|
||||
public void findOneWithPredicateReturnsEmptyWhenNoDataFound() { |
||||
|
||||
repository.findOne(person.firstname.eq("batman")) //
|
||||
.as(StepVerifier::create) //
|
||||
.verifyComplete(); |
||||
} |
||||
|
||||
@Test // DATAMONGO-2182
|
||||
public void findOneWithPredicateThrowsExceptionForNonUniqueResults() { |
||||
|
||||
repository.findOne(person.firstname.contains("e")) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectError(IncorrectResultSizeDataAccessException.class) //
|
||||
.verify(); |
||||
} |
||||
|
||||
@Test // DATAMONGO-2182
|
||||
public void findUsingAndShouldWork() { |
||||
|
||||
repository |
||||
.findAll(person.lastname.startsWith(oliver.getLastname()).and(person.firstname.startsWith(dave.getFirstname()))) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(dave) //
|
||||
.verifyComplete(); |
||||
} |
||||
|
||||
@Test // DATAMONGO-2182
|
||||
public void queryShouldTerminateWithUnsupportedOperationWithJoinOnDBref() { |
||||
|
||||
User user1 = new User(); |
||||
user1.setUsername("user-1"); |
||||
|
||||
User user2 = new User(); |
||||
user2.setUsername("user-2"); |
||||
|
||||
User user3 = new User(); |
||||
user3.setUsername("user-3"); |
||||
|
||||
operations.insertAll(Arrays.asList(user1, user2, user3)) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(3) //
|
||||
.verifyComplete(); |
||||
|
||||
Person person1 = new Person("Max", "The Mighty"); |
||||
person1.setCoworker(user1); |
||||
|
||||
Person person2 = new Person("Jack", "The Ripper"); |
||||
person2.setCoworker(user2); |
||||
|
||||
Person person3 = new Person("Bob", "The Builder"); |
||||
person3.setCoworker(user3); |
||||
|
||||
operations.save(person1) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(1) //
|
||||
.verifyComplete(); |
||||
operations.save(person2)//
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(1) //
|
||||
.verifyComplete(); |
||||
operations.save(person3) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(1) //
|
||||
.verifyComplete(); |
||||
|
||||
Flux<Person> result = new ReactiveSpringDataMongodbQuery<>(operations, Person.class).where() |
||||
.join(person.coworker, QUser.user).on(QUser.user.username.eq("user-2")).fetch(); |
||||
|
||||
result.as(StepVerifier::create) //
|
||||
.expectError(UnsupportedOperationException.class) //
|
||||
.verify(); |
||||
} |
||||
|
||||
@Test // DATAMONGO-2182
|
||||
public void queryShouldTerminateWithUnsupportedOperationOnJoinWithNoResults() { |
||||
|
||||
User user1 = new User(); |
||||
user1.setUsername("user-1"); |
||||
|
||||
User user2 = new User(); |
||||
user2.setUsername("user-2"); |
||||
|
||||
operations.insertAll(Arrays.asList(user1, user2)) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(2) //
|
||||
.verifyComplete(); |
||||
|
||||
Person person1 = new Person("Max", "The Mighty"); |
||||
person1.setCoworker(user1); |
||||
|
||||
Person person2 = new Person("Jack", "The Ripper"); |
||||
person2.setCoworker(user2); |
||||
|
||||
operations.save(person1) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(1) //
|
||||
.verifyComplete(); |
||||
; |
||||
operations.save(person2) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(1) //
|
||||
.verifyComplete(); |
||||
; |
||||
|
||||
Flux<Person> result = new ReactiveSpringDataMongodbQuery<>(operations, Person.class).where() |
||||
.join(person.coworker, QUser.user).on(QUser.user.username.eq("does-not-exist")).fetch(); |
||||
|
||||
result.as(StepVerifier::create) //
|
||||
.expectError(UnsupportedOperationException.class) //
|
||||
.verify(); |
||||
} |
||||
|
||||
@Test // DATAMONGO-2182
|
||||
public void springDataMongodbQueryShouldAllowElemMatchOnArrays() { |
||||
|
||||
Address adr1 = new Address("Hauptplatz", "4020", "Linz"); |
||||
Address adr2 = new Address("Stephansplatz", "1010", "Wien"); |
||||
Address adr3 = new Address("Tower of London", "EC3N 4AB", "London"); |
||||
|
||||
Person person1 = new Person("Max", "The Mighty"); |
||||
person1.setShippingAddresses(new LinkedHashSet<>(Arrays.asList(adr1, adr2))); |
||||
|
||||
Person person2 = new Person("Jack", "The Ripper"); |
||||
person2.setShippingAddresses(new LinkedHashSet<>(Arrays.asList(adr2, adr3))); |
||||
|
||||
operations.insertAll(Arrays.asList(person1, person2)) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(2) //
|
||||
.verifyComplete(); |
||||
|
||||
Flux<Person> result = new ReactiveSpringDataMongodbQuery<>(operations, Person.class).where() |
||||
.anyEmbedded(person.shippingAddresses, QAddress.address).on(QAddress.address.city.eq("London")).fetch(); |
||||
|
||||
result.as(StepVerifier::create) //
|
||||
.expectNext(person2) //
|
||||
.verifyComplete(); |
||||
} |
||||
|
||||
@Test // DATAMONGO-2182
|
||||
public void translatesExceptionsCorrectly() { |
||||
|
||||
ReactiveMongoOperations ops = new ReactiveMongoTemplate(dbFactory) { |
||||
|
||||
@Override |
||||
protected MongoDatabase doGetDatabase() { |
||||
throw new MongoException(18, "Authentication Failed"); |
||||
} |
||||
}; |
||||
|
||||
ReactiveMongoRepositoryFactory factory = new ReactiveMongoRepositoryFactory(ops); |
||||
MongoEntityInformation<Person, String> entityInformation = factory.getEntityInformation(Person.class); |
||||
repository = new ReactiveQuerydslMongoPredicateExecutor<>(entityInformation, ops); |
||||
|
||||
repository.findOne(person.firstname.contains("batman")) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectError(PermissionDeniedDataAccessException.class) //
|
||||
.verify(); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue