DATAMONGO-2182 - Polishing.

Introduce base class for common Querydsl query execution tasks that can be used for imperative and reactive implementation.
Fix test issues due to context caching where indices are not recreated when dropping the collection during test setup.
Update reference documentation.

Original Pull Request: #635
This commit is contained in:
Christoph Strobl
2019-01-23 10:31:12 +01:00
parent 16051106c0
commit e0a12d77f7
8 changed files with 270 additions and 141 deletions

View File

@@ -22,23 +22,18 @@ 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.domain.Sort.Order;
import org.springframework.data.mongodb.core.MongoOperations;
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.SimpleEntityPathResolver;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.data.repository.support.PageableExecutionUtils;
import org.springframework.util.Assert;
import com.querydsl.core.NonUniqueResultException;
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.
@@ -50,10 +45,9 @@ import com.querydsl.core.types.dsl.PathBuilder;
* @author Mark Paluch
* @since 2.0
*/
public class QuerydslMongoPredicateExecutor<T> implements QuerydslPredicateExecutor<T> {
public class QuerydslMongoPredicateExecutor<T> extends QuerydslPredicateExecutorSupport<T>
implements QuerydslPredicateExecutor<T> {
private final PathBuilder<T> builder;
private final EntityInformation<T, ?> entityInformation;
private final MongoOperations mongoOperations;
/**
@@ -80,12 +74,8 @@ public class QuerydslMongoPredicateExecutor<T> implements QuerydslPredicateExecu
public QuerydslMongoPredicateExecutor(MongoEntityInformation<T, ?> entityInformation, MongoOperations 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;
super(mongoOperations.getConverter(), pathBuilderFor(resolver.createPath(entityInformation.getJavaType())),
entityInformation);
this.mongoOperations = mongoOperations;
}
@@ -210,7 +200,7 @@ public class QuerydslMongoPredicateExecutor<T> implements QuerydslPredicateExecu
* @return
*/
private SpringDataMongodbQuery<T> createQuery() {
return new SpringDataMongodbQuery<>(mongoOperations, entityInformation.getJavaType());
return new SpringDataMongodbQuery<>(mongoOperations, typeInformation().getJavaType());
}
/**
@@ -235,32 +225,7 @@ public class QuerydslMongoPredicateExecutor<T> implements QuerydslPredicateExecu
*/
private SpringDataMongodbQuery<T> applySorting(SpringDataMongodbQuery<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);
toOrderSpecifiers(sort).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);
}
}

View File

@@ -0,0 +1,92 @@
/*
* 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 java.util.List;
import java.util.stream.Collectors;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.querydsl.QSort;
import org.springframework.data.repository.core.EntityInformation;
import com.querydsl.core.types.EntityPath;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.dsl.PathBuilder;
/**
* @author Christoph Strobl
* @since 2.2
*/
abstract class QuerydslPredicateExecutorSupport<T> {
private final SpringDataMongodbSerializer serializer;
private final PathBuilder<T> builder;
private final EntityInformation<T, ?> entityInformation;
QuerydslPredicateExecutorSupport(MongoConverter converter, PathBuilder<T> builder,
EntityInformation<T, ?> entityInformation) {
this.serializer = new SpringDataMongodbSerializer(converter);
this.builder = builder;
this.entityInformation = entityInformation;
}
protected static <E> PathBuilder<E> pathBuilderFor(EntityPath<E> path) {
return new PathBuilder<>(path.getType(), path.getMetadata());
}
protected EntityInformation<T, ?> typeInformation() {
return entityInformation;
}
protected SpringDataMongodbSerializer mongodbSerializer() {
return serializer;
}
/**
* Transforms a plain {@link Order} into a Querydsl specific {@link OrderSpecifier}.
*
* @param order
* @return
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
protected 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);
}
/**
* Converts the given {@link Sort} to {@link OrderSpecifier}.
*
* @param sort
* @return
*/
protected List<OrderSpecifier<?>> toOrderSpecifiers(Sort sort) {
if (sort instanceof QSort) {
return ((QSort) sort).getOrderSpecifiers();
}
return sort.stream().map(this::toOrder).collect(Collectors.toList());
}
}

View File

@@ -18,36 +18,29 @@ 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
* @author Christoph Strobl
* @since 2.2
*/
public class ReactiveQuerydslMongoPredicateExecutor<T> implements ReactiveQuerydslPredicateExecutor<T> {
public class ReactiveQuerydslMongoPredicateExecutor<T> extends QuerydslPredicateExecutorSupport<T>
implements ReactiveQuerydslPredicateExecutor<T> {
private final PathBuilder<T> builder;
private final EntityInformation<T, ?> entityInformation;
private final ReactiveMongoOperations mongoOperations;
/**
@@ -60,6 +53,7 @@ public class ReactiveQuerydslMongoPredicateExecutor<T> implements ReactiveQueryd
*/
public ReactiveQuerydslMongoPredicateExecutor(MongoEntityInformation<T, ?> entityInformation,
ReactiveMongoOperations mongoOperations) {
this(entityInformation, mongoOperations, SimpleEntityPathResolver.INSTANCE);
}
@@ -74,12 +68,8 @@ public class ReactiveQuerydslMongoPredicateExecutor<T> implements ReactiveQueryd
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;
super(mongoOperations.getConverter(), pathBuilderFor(resolver.createPath(entityInformation.getJavaType())),
entityInformation);
this.mongoOperations = mongoOperations;
}
@@ -185,10 +175,9 @@ public class ReactiveQuerydslMongoPredicateExecutor<T> implements ReactiveQueryd
* @return
*/
private ReactiveSpringDataMongodbQuery<T> createQuery() {
SpringDataMongodbSerializer serializer = new SpringDataMongodbSerializer(mongoOperations.getConverter());
Class<T> javaType = entityInformation.getJavaType();
return new ReactiveSpringDataMongodbQuery<>(serializer, mongoOperations, javaType,
Class<T> javaType = typeInformation().getJavaType();
return new ReactiveSpringDataMongodbQuery<>(mongodbSerializer(), mongoOperations, javaType,
mongoOperations.getCollectionName(javaType));
}
@@ -201,32 +190,8 @@ public class ReactiveQuerydslMongoPredicateExecutor<T> implements ReactiveQueryd
*/
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);
toOrderSpecifiers(sort).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);
}
}

View File

@@ -29,6 +29,7 @@ import org.springframework.data.mongodb.core.query.Query;
import org.springframework.lang.Nullable;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import com.querydsl.core.JoinExpression;
import com.querydsl.core.QueryMetadata;
@@ -45,8 +46,8 @@ 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
* @author Christoph Strobl
* @since 2.2
*/
class ReactiveSpringDataMongodbQuery<K> extends QuerydslAbstractMongodbQuery<K, ReactiveSpringDataMongodbQuery<K>> {
@@ -56,22 +57,18 @@ class ReactiveSpringDataMongodbQuery<K> extends QuerydslAbstractMongodbQuery<K,
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);
this(new SpringDataMongodbSerializer(mongoOperations.getConverter()), mongoOperations, entityClass, null);
}
ReactiveSpringDataMongodbQuery(MongodbDocumentSerializer serializer, ReactiveMongoOperations mongoOperations,
Class<? extends K> entityClass, String collection) {
Class<? extends K> entityClass, @Nullable String collection) {
super(serializer);
this.entityClass = (Class<K>) entityClass;
this.mongoOperations = mongoOperations;
this.find = mongoOperations.query(this.entityClass).inCollection(collection);
this.find = StringUtils.hasText(collection) ? mongoOperations.query(this.entityClass).inCollection(collection)
: mongoOperations.query(this.entityClass);
}
/**
@@ -79,7 +76,7 @@ class ReactiveSpringDataMongodbQuery<K> extends QuerydslAbstractMongodbQuery<K,
*
* @return {@link Flux} emitting all query results or {@link Flux#empty()} if there are none.
*/
public Flux<K> fetch() {
Flux<K> fetch() {
return createQuery().flatMapMany(it -> find.matching(it).all());
}
@@ -89,7 +86,7 @@ class ReactiveSpringDataMongodbQuery<K> extends QuerydslAbstractMongodbQuery<K,
* @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() {
Mono<K> fetchOne() {
return createQuery().flatMap(it -> find.matching(it).one());
}
@@ -98,7 +95,7 @@ class ReactiveSpringDataMongodbQuery<K> extends QuerydslAbstractMongodbQuery<K,
*
* @return {@link Mono} emitting the first query result count. Emits always a count even item.
*/
public Mono<Long> fetchCount() {
Mono<Long> fetchCount() {
return createQuery().flatMap(it -> find.matching(it).count());
}
@@ -109,7 +106,7 @@ class ReactiveSpringDataMongodbQuery<K> extends QuerydslAbstractMongodbQuery<K,
* @param target join target
* @return new instance of {@link QuerydslJoinBuilder}.
*/
public <T> QuerydslJoinBuilder<ReactiveSpringDataMongodbQuery<K>, K, T> join(Path<T> ref, Path<T> target) {
<T> QuerydslJoinBuilder<ReactiveSpringDataMongodbQuery<K>, K, T> join(Path<T> ref, Path<T> target) {
return new QuerydslJoinBuilder<>(getQueryMixin(), ref, target);
}
@@ -120,8 +117,9 @@ class ReactiveSpringDataMongodbQuery<K> extends QuerydslAbstractMongodbQuery<K,
* @param target join target
* @return new instance of {@link QuerydslJoinBuilder}.
*/
public <T> QuerydslJoinBuilder<ReactiveSpringDataMongodbQuery<K>, K, T> join(CollectionPathBase<?, T, ?> ref,
<T> QuerydslJoinBuilder<ReactiveSpringDataMongodbQuery<K>, K, T> join(CollectionPathBase<?, T, ?> ref,
Path<T> target) {
return new QuerydslJoinBuilder<>(getQueryMixin(), ref, target);
}
@@ -132,8 +130,9 @@ class ReactiveSpringDataMongodbQuery<K> extends QuerydslAbstractMongodbQuery<K,
* @param target target must not be {@literal null}.
* @return new instance of {@link QuerydslAnyEmbeddedBuilder}.
*/
public <T> QuerydslAnyEmbeddedBuilder<ReactiveSpringDataMongodbQuery<K>, K> anyEmbedded(
<T> QuerydslAnyEmbeddedBuilder<ReactiveSpringDataMongodbQuery<K>, K> anyEmbedded(
Path<? extends Collection<T>> collection, Path<T> target) {
return new QuerydslAnyEmbeddedBuilder<>(getQueryMixin(), collection);
}
@@ -259,6 +258,7 @@ class ReactiveSpringDataMongodbQuery<K> extends QuerydslAbstractMongodbQuery<K,
* @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));
}
@@ -270,7 +270,7 @@ class ReactiveSpringDataMongodbQuery<K> extends QuerydslAbstractMongodbQuery<K,
final Path<?> source;
public NoMatchException(Path<?> source) {
NoMatchException(Path<?> source) {
this.source = source;
}

View File

@@ -15,7 +15,7 @@
*/
package org.springframework.data.mongodb.repository;
import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.api.Assertions.offset;
import static org.springframework.data.domain.Sort.Direction.*;
import static org.springframework.data.mongodb.test.util.Assertions.assertThat;
@@ -32,14 +32,14 @@ import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.reactivestreams.Publisher;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
@@ -50,18 +50,23 @@ import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResult;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.config.AbstractReactiveMongoConfiguration;
import org.springframework.data.mongodb.core.CollectionOptions;
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.repository.Person.Sex;
import org.springframework.data.mongodb.repository.support.ReactiveMongoRepositoryFactory;
import org.springframework.data.mongodb.repository.support.SimpleReactiveMongoRepository;
import org.springframework.data.mongodb.test.util.MongoTestUtils;
import org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.ClassUtils;
import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoClients;
/**
* Test for {@link ReactiveMongoRepository} query methods.
@@ -70,45 +75,74 @@ import org.springframework.util.ClassUtils;
* @author Christoph Strobl
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:reactive-infrastructure.xml")
public class ReactiveMongoRepositoryTests implements BeanClassLoaderAware, BeanFactoryAware {
@ContextConfiguration
public class ReactiveMongoRepositoryTests {
@Autowired ReactiveMongoTemplate template;
ReactiveMongoRepositoryFactory factory;
ClassLoader classLoader;
BeanFactory beanFactory;
ReactivePersonRepository repository;
ReactiveContactRepository contactRepository;
ReactiveCappedCollectionRepository cappedRepository;
@Autowired ReactivePersonRepository repository;
@Autowired ReactiveContactRepository contactRepository;
@Autowired ReactiveCappedCollectionRepository cappedRepository;
Person dave, oliver, carter, boyd, stefan, leroi, alicia;
QPerson person = new QPerson("person");
QPerson person = QPerson.person;
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader == null ? ClassUtils.getDefaultClassLoader() : classLoader;
@Configuration
static class Config extends AbstractReactiveMongoConfiguration {
@Bean
@Override
public MongoClient reactiveMongoClient() {
return MongoClients.create();
}
@Override
protected String getDatabaseName() {
return "reactive";
}
@Bean
ReactiveMongoRepositoryFactory factory(ReactiveMongoOperations template, BeanFactory beanFactory) {
ReactiveMongoRepositoryFactory factory = new ReactiveMongoRepositoryFactory(template);
factory.setRepositoryBaseClass(SimpleReactiveMongoRepository.class);
factory.setBeanClassLoader(beanFactory.getClass().getClassLoader());
factory.setBeanFactory(beanFactory);
factory.setEvaluationContextProvider(QueryMethodEvaluationContextProvider.DEFAULT);
return factory;
}
@Bean
ReactivePersonRepository reactivePersonRepository(ReactiveMongoRepositoryFactory factory) {
return factory.getRepository(ReactivePersonRepository.class);
}
@Bean
ReactiveContactRepository reactiveContactRepository(ReactiveMongoRepositoryFactory factory) {
return factory.getRepository(ReactiveContactRepository.class);
}
@Bean
ReactiveCappedCollectionRepository reactiveCappedCollectionRepository(ReactiveMongoRepositoryFactory factory) {
return factory.getRepository(ReactiveCappedCollectionRepository.class);
}
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
@BeforeClass
public static void cleanDb() {
try (MongoClient client = MongoClients.create()) {
MongoTestUtils.createOrReplaceCollectionNow("reactive", "person", client);
MongoTestUtils.createOrReplaceCollectionNow("reactive", "capped", client);
}
}
@Before
public void setUp() throws Exception {
factory = new ReactiveMongoRepositoryFactory(template);
factory.setRepositoryBaseClass(SimpleReactiveMongoRepository.class);
factory.setBeanClassLoader(classLoader);
factory.setBeanFactory(beanFactory);
factory.setEvaluationContextProvider(QueryMethodEvaluationContextProvider.DEFAULT);
repository = factory.getRepository(ReactivePersonRepository.class);
contactRepository = factory.getRepository(ReactiveContactRepository.class);
cappedRepository = factory.getRepository(ReactiveCappedCollectionRepository.class);
StepVerifier.create(repository.deleteAll()).verifyComplete();
repository.deleteAll().as(StepVerifier::create).verifyComplete();
dave = new Person("Dave", "Matthews", 42);
oliver = new Person("Oliver August", "Matthews", 4);

View File

@@ -22,14 +22,17 @@ import java.util.Arrays;
import java.util.LinkedHashSet;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
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.config.AbstractReactiveMongoConfiguration;
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.repository.Address;
@@ -39,20 +42,24 @@ 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.data.mongodb.test.util.MongoTestUtils;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import com.mongodb.MongoException;
import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoClients;
import com.mongodb.reactivestreams.client.MongoDatabase;
/**
* Tests for {@link ReactiveQuerydslMongoPredicateExecutor}.
*
* @author Mark Paluch
* @author Christoph Strobl
*/
@RunWith(SpringRunner.class)
@ContextConfiguration("classpath:reactive-infrastructure.xml")
public class ReactiveQuerydslMongoPredicateExecutorIntegrationTests {
@ContextConfiguration
public class ReactiveQuerydslMongoPredicateExecutorTests {
@Autowired ReactiveMongoOperations operations;
@Autowired ReactiveMongoDatabaseFactory dbFactory;
@@ -62,6 +69,30 @@ public class ReactiveQuerydslMongoPredicateExecutorIntegrationTests {
Person dave, oliver, carter;
QPerson person;
@Configuration
static class Config extends AbstractReactiveMongoConfiguration {
@Override
public MongoClient reactiveMongoClient() {
return MongoClients.create();
}
@Override
protected String getDatabaseName() {
return "reactive";
}
}
@BeforeClass
public static void cleanDb() {
try (MongoClient client = MongoClients.create()) {
MongoTestUtils.createOrReplaceCollectionNow("reactive", "person", client);
MongoTestUtils.createOrReplaceCollectionNow("reactive", "user", client);
}
}
@Before
public void setup() {
@@ -98,6 +129,20 @@ public class ReactiveQuerydslMongoPredicateExecutorIntegrationTests {
.verifyComplete();
}
@Test // DATAMONGO-2182
public void shouldSupportCountWithPredicate() {
repository.count(person.firstname.eq("Dave")) //
.as(StepVerifier::create) //
.expectNext(1L) //
.verifyComplete();
repository.count(person.firstname.eq("Unknown")) //
.as(StepVerifier::create) //
.expectNext(0L) //
.verifyComplete();
}
@Test // DATAMONGO-2182
public void shouldSupportFindAllWithPredicateAndSort() {

View File

@@ -16,6 +16,7 @@
package org.springframework.data.mongodb.test.util;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.bson.Document;
@@ -81,6 +82,23 @@ public class MongoTestUtils {
.then(Mono.from(database.createCollection(collectionName)));
}
/**
* Create a {@link com.mongodb.client.MongoCollection} if it does not exist, or drop and recreate it if it does and
* verify operation result.
*
* @param dbName must not be {@literal null}.
* @param collectionName must not be {@literal null}.
* @param client must not be {@literal null}.
*/
public static void createOrReplaceCollectionNow(String dbName, String collectionName,
com.mongodb.reactivestreams.client.MongoClient client) {
createOrReplaceCollection(dbName, collectionName, client) //
.as(StepVerifier::create) //
.expectNext(Success.SUCCESS) //
.verifyComplete();
}
/**
* Create a new {@link com.mongodb.MongoClient} with defaults suitable for replica set usage.
*

View File

@@ -132,8 +132,6 @@ It supports the following features:
* <<mongodb.reactive.repositories.queries.type-safe>>
* <<projections>>
WARNING: Reactive Repositories do not support type-safe query methods that use `Querydsl`.
[[mongodb.reactive.repositories.queries.geo-spatial]]
=== Geo-spatial Repository Queries
@@ -203,7 +201,12 @@ public interface PersonRepository extends ReactiveMongoRepository<Person, String
[[mongodb.reactive.repositories.queries.type-safe]]
=== Type-safe Query Methods
Reactive MongoDB repository support integrates with the http://www.querydsl.com/[Querydsl] project, which provides a way to perform type-safe queries. To quote from the project description, "Instead of writing queries as inline strings or externalizing them into XML files they are constructed via a fluent API." It provides the following features:
Reactive MongoDB repository support integrates with the http://www.querydsl.com/[Querydsl] project, which provides a way to perform type-safe queries.
[quote, Querydsl Team]
Instead of writing queries as inline strings or externalizing them into XML files they are constructed via a fluent API.
It provides the following features:
* Code completion in the IDE (all properties, methods, and operations can be expanded in your favorite Java IDE).
* Almost no syntactically invalid queries allowed (type-safe on all levels).
@@ -211,21 +214,24 @@ Reactive MongoDB repository support integrates with the http://www.querydsl.com/
* Adapts better to refactoring changes in domain types.
* Incremental query definition is easier.
See the http://www.querydsl.com/static/querydsl/latest/reference/html/[QueryDSL documentation] for how to bootstrap your environment for APT-based code generation using Maven or Ant.
See the http://www.querydsl.com/static/querydsl/latest/reference/html/[Querydsl documentation] for how to bootstrap your environment for APT-based code generation using Maven or Ant.
QueryDSL lets you write queries such as the following:
The Querydsl repository support lets you write and execute queries such as the following:
[source,java]
----
QPerson person = new QPerson("person");
QPerson person = QPerson.person;
Flux<Person> result = repository.findAll(person.address.zipCode.eq("C0123"));
----
`QPerson` is a class that is generated by the Java annotation post-processing tool. It is a `Predicate` that lets you write type-safe queries. Notice that there are no strings in the query other than the `C0123` value.
`QPerson` is a class that is generated by the Java annotation post-processing tool. It is a `Predicate` that lets you write type-safe queries.
Note that there are no strings in the query other than the `C0123` value.
You can use the generated `Predicate` class by using the `ReactiveQuerydslPredicateExecutor` interface, which the following listing shows:
.The Gateway to Reactive Querydsl - The ReactiveQuerydslPredicateExecutor
====
[source,java]
----
public interface ReactiveQuerydslPredicateExecutor<T> {
@@ -245,9 +251,12 @@ public interface ReactiveQuerydslPredicateExecutor<T> {
Mono<Boolean> exists(Predicate predicate);
}
----
====
To use this in your repository implementation, add it to the list of repository interfaces from which your interface inherits, as the following example shows:
.Reactive Querydsl Respository Declaration
====
[source,java]
----
public interface PersonRepository extends ReactiveMongoRepository<Person, String>, ReactiveQuerydslPredicateExecutor<Person> {
@@ -255,5 +264,6 @@ public interface PersonRepository extends ReactiveMongoRepository<Person, String
// additional query methods go here
}
----
====
NOTE: Please note that joins (DBRef's) are not supported with Reactive MongoDB support.