DATAMONGO-1761 - Polishing.

Refactor MongoConverter.mapValueToTargetType to imperative method instead of returning a function for easier consumption. Adapt return types in Javadoc to the actual return type. Remove undocumented type parameters. Add overload using reified entity and return type generics. Slight documentation tweaks.

Original pull request: #494.
Related pull request: #514.
This commit is contained in:
Mark Paluch
2017-11-06 20:57:27 +01:00
parent 45fa1e50f9
commit 5cca849ecb
20 changed files with 252 additions and 165 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 the original author or authors.
* Copyright 2017-2018 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.
@@ -23,9 +23,13 @@ import org.bson.codecs.configuration.CodecRegistry;
import org.springframework.util.Assert;
/**
* Provider interface to obtain {@link CodecRegistry} from the underlying MongoDB Java driver.
*
* @author Christoph Strobl
* @author Mark Paluch
* @since 2.1
*/
@FunctionalInterface
public interface CodecRegistryProvider {
/**
@@ -50,7 +54,7 @@ public interface CodecRegistryProvider {
/**
* Get the {@link Codec} registered for the given {@literal type} or an {@link Optional#empty() empty Optional}
* instead.
*
*
* @param type must not be {@literal null}.
* @param <T>
* @return never {@literal null}.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2013 the original author or authors.
* Copyright 2011-2018 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.
@@ -25,15 +25,16 @@ import com.mongodb.client.MongoDatabase;
/**
* Interface for factories creating {@link DB} instances.
*
*
* @author Mark Pollack
* @author Thomas Darimont
* @author Christoph Strobl
*/
public interface MongoDbFactory extends CodecRegistryProvider {
/**
* Creates a default {@link DB} instance.
*
*
* @return
* @throws DataAccessException
*/
@@ -41,7 +42,7 @@ public interface MongoDbFactory extends CodecRegistryProvider {
/**
* Creates a {@link DB} instance to access the database with the given name.
*
*
* @param dbName must not be {@literal null} or empty.
* @return
* @throws DataAccessException
@@ -50,13 +51,18 @@ public interface MongoDbFactory extends CodecRegistryProvider {
/**
* Exposes a shared {@link MongoExceptionTranslator}.
*
*
* @return will never be {@literal null}.
*/
PersistenceExceptionTranslator getExceptionTranslator();
DB getLegacyDb();
/**
* Get the underlying {@link CodecRegistry} used by the MongoDB Java driver.
*
* @return never {@literal null}.
*/
@Override
default CodecRegistry getCodecRegistry() {
return getDb().getCodecRegistry();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2018 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.
@@ -56,6 +56,11 @@ public interface ReactiveMongoDatabaseFactory extends CodecRegistryProvider {
*/
PersistenceExceptionTranslator getExceptionTranslator();
/**
* Get the underlying {@link CodecRegistry} used by the reactive MongoDB Java driver.
*
* @return never {@literal null}.
*/
@Override
default CodecRegistry getCodecRegistry() {
return getMongoDatabase().getCodecRegistry();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 the original author or authors.
* Copyright 2017-2018 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.
@@ -258,7 +258,7 @@ public interface ExecutableFindOperation {
* to obtain the domain type. <br />
* Using {@link Object} also works for non strictly typed fields. Eg. a mixture different types like fields using
* {@link String} in one {@link org.bson.Document} while {@link Long} in another.</dd>
* <dt>Any Simple type like {@link String}, {@link Long}, ...</dt>
* <dt>Any Simple type like {@link String} or {@link Long}.</dt>
* <dd>The result is mapped directly by the MongoDB Java driver and the {@link org.bson.codecs.CodeCodec Codecs} in
* place. This works only for results where all documents considered for the operation use the very same type for
* the field.</dd>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 the original author or authors.
* Copyright 2017-2018 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.
@@ -209,12 +209,13 @@ class ExecutableFindOperationSupport implements ExecutableFindOperation {
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableFindOperation.FindDistinct#distinct(java.lang.String)
*/
@SuppressWarnings("unchecked")
@Override
public TerminatingDistinct<Object> distinct(String field) {
Assert.notNull(field, "Field must not be null!");
return new DistinctOperationSupport<>(this, field);
return new DistinctOperationSupport(this, field);
}
private List<T> doFind(@Nullable CursorPreparer preparer) {
@@ -287,9 +288,9 @@ class ExecutableFindOperationSupport implements ExecutableFindOperation {
static class DistinctOperationSupport<T> implements TerminatingDistinct<T> {
private final String field;
private final ExecutableFindSupport delegate;
private final ExecutableFindSupport<T> delegate;
public DistinctOperationSupport(ExecutableFindSupport delegate, String field) {
public DistinctOperationSupport(ExecutableFindSupport<T> delegate, String field) {
this.delegate = delegate;
this.field = field;
@@ -300,11 +301,12 @@ class ExecutableFindOperationSupport implements ExecutableFindOperation {
* @see org.springframework.data.mongodb.core.ExecutableFindOperation.DistinctWithProjection#as(java.lang.Class)
*/
@Override
@SuppressWarnings("unchecked")
public <R> TerminatingDistinct<R> as(Class<R> resultType) {
Assert.notNull(resultType, "ResultType must not be null!");
return new DistinctOperationSupport((ExecutableFindSupport) delegate.as(resultType), field);
return new DistinctOperationSupport<>((ExecutableFindSupport) delegate.as(resultType), field);
}
/*
@@ -316,7 +318,7 @@ class ExecutableFindOperationSupport implements ExecutableFindOperation {
Assert.notNull(query, "Query must not be null!");
return new DistinctOperationSupport((ExecutableFindSupport) delegate.matching(query), field);
return new DistinctOperationSupport<>((ExecutableFindSupport<T>) delegate.matching(query), field);
}
/*

View File

@@ -761,7 +761,6 @@ public interface MongoOperations extends FluentMongoOperations {
* @param field the name of the field to inspect for distinct values. Must not be {@literal null}.
* @param collection the explicit name of the actual {@link MongoCollection}. Must not be {@literal null}.
* @param resultClass the result type. Must not be {@literal null}.
* @param <T>
* @return never {@literal null}.
* @since 2.1
*/

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2010-2017 the original author or authors.
* Copyright 2010-2018 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.
@@ -121,7 +121,6 @@ import org.springframework.util.ObjectUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
import com.mongodb.Function;
import com.mongodb.MongoClient;
import com.mongodb.MongoException;
import com.mongodb.ReadPreference;
@@ -803,7 +802,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
}
/*
* (non-Javadoc)
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.MongoOperations#findDistinct(org.springframework.data.mongodb.core.query.Query, java.lang.String, java.lang.Class, java.lang.Class)
*/
@Override
@@ -816,6 +815,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
* @see org.springframework.data.mongodb.core.MongoOperations#findDistinct(org.springframework.data.mongodb.core.query.Query, java.lang.String, java.lang.String, java.lang.Class, java.lang.Class)
*/
@Override
@SuppressWarnings("unchecked")
public <T> List<T> findDistinct(Query query, String field, String collectionName, Class<?> entityClass,
Class<T> resultClass) {
@@ -830,20 +830,24 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), entity);
String mappedFieldName = queryMapper.getMappedFields(new Document(field, 1), entity).keySet().iterator().next();
Class<?> mongoDriverCompatibleType = getMongoDbFactory().getCodecFor(resultClass).map(Codec::getEncoderClass)
Class<T> mongoDriverCompatibleType = getMongoDbFactory().getCodecFor(resultClass).map(Codec::getEncoderClass)
.orElse((Class) BsonValue.class);
MongoIterable<?> result = execute((db) -> {
DistinctIterable<?> iterable = db.getCollection(collectionName).distinct(mappedFieldName, mappedQuery,
DistinctIterable<T> iterable = db.getCollection(collectionName).distinct(mappedFieldName, mappedQuery,
mongoDriverCompatibleType);
return query.getCollation().isPresent()
? iterable.collation(query.getCollation().map(Collation::toMongoCollation).get()) : iterable;
return query.getCollation().map(Collation::toMongoCollation).map(iterable::collation).orElse(iterable);
});
if (resultClass == Object.class || mongoDriverCompatibleType != resultClass) {
result = result.map(mapDistinctResult(getMostSpecificConversionTargetType(resultClass, entityClass, field)));
MongoConverter converter = getConverter();
DefaultDbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory);
result = result.map((source) -> converter.mapValueToTargetType(source,
getMostSpecificConversionTargetType(resultClass, entityClass, field), dbRefResolver));
}
try {
@@ -860,7 +864,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
* @return the most specific conversion target type depending on user preference and domain type property.
* @since 2.1
*/
private Class<?> getMostSpecificConversionTargetType(Class<?> userType, Class<?> domainType, String field) {
private static Class<?> getMostSpecificConversionTargetType(Class<?> userType, Class<?> domainType, String field) {
Class<?> conversionTargetType = userType;
try {
@@ -879,16 +883,6 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
return conversionTargetType;
}
/**
* @param targetType the desired conversion target type.
* @return new {@link Function} converting {@link BsonValue} into desired target type.
* @since 2.1
*/
private <S, T> Function<S, T> mapDistinctResult(Class<T> targetType) {
return (source) -> getConverter().mapValueToTargetType(targetType, new DefaultDbRefResolver(mongoDbFactory))
.apply(source);
}
@Override
public <T> GeoResults<T> geoNear(NearQuery near, Class<T> entityClass) {
return geoNear(near, entityClass, determineCollectionName(entityClass));

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 the original author or authors.
* Copyright 2017-2018 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 the original author or authors.
* Copyright 2017-2018 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.
@@ -217,6 +217,7 @@ class ReactiveFindOperationSupport implements ReactiveFindOperation {
preparer != null ? preparer : getCursorPreparer(query));
}
@SuppressWarnings("unchecked")
private Flux<T> doFindDistinct(String field) {
return template.findDistinct(query, field, getCollectionName(), domainType,
@@ -259,7 +260,7 @@ class ReactiveFindOperationSupport implements ReactiveFindOperation {
Assert.notNull(resultType, "ResultType must not be null!");
return new DistinctOperationSupport((ReactiveFindSupport) delegate.as(resultType), field);
return new DistinctOperationSupport<>((ReactiveFindSupport) delegate.as(resultType), field);
}
/*
@@ -267,11 +268,12 @@ class ReactiveFindOperationSupport implements ReactiveFindOperation {
* @see org.springframework.data.mongodb.core.ReactiveFindOperation.DistinctWithQuery#matching(org.springframework.data.mongodb.core.query.Query)
*/
@Override
@SuppressWarnings("unchecked")
public TerminatingDistinct<T> matching(Query query) {
Assert.notNull(query, "Query must not be null!");
return new DistinctOperationSupport((ReactiveFindSupport) delegate.matching(query), field);
return new DistinctOperationSupport<>((ReactiveFindSupport<T>) delegate.matching(query), field);
}
/*

View File

@@ -379,7 +379,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
/**
* Finds the distinct values for a specified {@literal field} across a single {@link MongoCollection} or view and
* returns the results in a {@link List}.
* returns the results in a {@link Flux}.
*
* @param field the name of the field to inspect for distinct values. Must not be {@literal null}.
* @param entityClass the domain type used for determining the actual {@link MongoCollection}. Must not be
@@ -394,7 +394,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
/**
* Finds the distinct values for a specified {@literal field} across a single {@link MongoCollection} or view and
* returns the results in a {@link List}.
* returns the results in a {@link Flux}.
*
* @param query filter {@link Query} to restrict search. Must not be {@literal null}.
* @param field the name of the field to inspect for distinct values. Must not be {@literal null}.
@@ -408,7 +408,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
/**
* Finds the distinct values for a specified {@literal field} across a single {@link MongoCollection} or view and
* returns the results in a {@link List}.
* returns the results in a {@link Flux}.
*
* @param query filter {@link Query} to restrict search. Must not be {@literal null}.
* @param field the name of the field to inspect for distinct values. Must not be {@literal null}.
@@ -423,14 +423,13 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
/**
* Finds the distinct values for a specified {@literal field} across a single {@link MongoCollection} or view and
* returns the results in a {@link List}.
* returns the results in a {@link Flux}.
*
* @param query filter {@link Query} to restrict search. Must not be {@literal null}.
* @param field the name of the field to inspect for distinct values. Must not be {@literal null}.
* @param collection the explicit name of the actual {@link MongoCollection}. Must not be {@literal null}.
* @param resultClass the result type. Must not be {@literal null}.
* @param <T>
* @return
* @return never {@literal null}.
* @since 2.1
*/
default <T> Flux<T> findDistinct(Query query, String field, String collection, Class<T> resultClass) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016-2017 the original author or authors.
* Copyright 2016-2018 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.
@@ -24,17 +24,8 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -692,6 +683,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findDistinct(org.springframework.data.mongodb.core.query.Query, java.lang.String, java.lang.String, java.lang.Class, java.lang.Class)
*/
@SuppressWarnings("unchecked")
public <T> Flux<T> findDistinct(Query query, String field, String collectionName, Class<?> entityClass,
Class<T> resultClass) {
@@ -706,23 +698,25 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), entity);
String mappedFieldName = queryMapper.getMappedFields(new Document(field, 1), entity).keySet().iterator().next();
Class<?> mongoDriverCompatibleType = mongoDatabaseFactory.getCodecFor(resultClass).map(Codec::getEncoderClass)
Class<T> mongoDriverCompatibleType = mongoDatabaseFactory.getCodecFor(resultClass).map(Codec::getEncoderClass)
.orElse((Class) BsonValue.class);
Flux result = execute(collectionName, collection -> {
Flux<?> result = execute(collectionName, collection -> {
DistinctPublisher publisher = collection.distinct(mappedFieldName, mappedQuery, mongoDriverCompatibleType);
DistinctPublisher<T> publisher = collection.distinct(mappedFieldName, mappedQuery, mongoDriverCompatibleType);
return query.getCollation().isPresent()
? publisher.collation(query.getCollation().map(Collation::toMongoCollation).get()) : publisher;
return query.getCollation().map(Collation::toMongoCollation).map(publisher::collation).orElse(publisher);
});
if (resultClass == Object.class || mongoDriverCompatibleType != resultClass) {
result = result.map(
getConverter().mapValueToTargetType(getMostSpecificConversionTargetType(resultClass, entityClass, field), NO_OP_REF_RESOLVER));
Class<?> targetType = getMostSpecificConversionTargetType(resultClass, entityClass, field);
MongoConverter converter = getConverter();
result = result.map(it -> converter.mapValueToTargetType(it, targetType, NO_OP_REF_RESOLVER));
}
return result;
return (Flux<T>) result;
}
/**
@@ -732,7 +726,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
* @return the most specific conversion target type depending on user preference and domain type property.
* @since 2.1
*/
private Class<?> getMostSpecificConversionTargetType(Class<?> userType, Class<?> domainType, String field) {
private static Class<?> getMostSpecificConversionTargetType(Class<?> userType, Class<?> domainType, String field) {
Class<?> conversionTargetType = userType;
try {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2010-2016 the original author or authors.
* Copyright 2010-2018 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.
@@ -15,8 +15,6 @@
*/
package org.springframework.data.mongodb.core.convert;
import java.util.function.Function;
import org.bson.BsonValue;
import org.bson.Document;
import org.bson.conversions.Bson;
@@ -26,17 +24,19 @@ import org.springframework.data.convert.TypeMapper;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.util.BsonUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import com.mongodb.DBRef;
/**
* Central Mongo specific converter interface which combines {@link MongoWriter} and {@link MongoReader}.
*
* Central Mongo specific converter interface which combines {@link MongoWriter} and {@link EntityReader}.
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
* @author Mark Paluch
*/
public interface MongoConverter
extends EntityConverter<MongoPersistentEntity<?>, MongoPersistentProperty, Object, Bson>, MongoWriter<Object>,
@@ -45,7 +45,7 @@ public interface MongoConverter
/**
* Returns thw {@link TypeMapper} being used to write type information into {@link Document}s created with that
* converter.
*
*
* @return will never be {@literal null}.
*/
MongoTypeMapper getTypeMapper();
@@ -58,50 +58,49 @@ public interface MongoConverter
* @param dbRefResolver must not be {@literal null}.
* @param <S>
* @param <T>
* @return new typed {@link com.mongodb.Function}.
* @return new typed {@link java.util.function.Function}.
* @throws IllegalArgumentException if {@literal targetType} is {@literal null}.
* @since 2.1
*/
default <S, T> Function<S, T> mapValueToTargetType(Class<T> targetType, DbRefResolver dbRefResolver) {
@SuppressWarnings("unchecked")
@Nullable
default <S, T> T mapValueToTargetType(S source, Class<T> targetType, DbRefResolver dbRefResolver) {
Assert.notNull(targetType, "TargetType must not be null!");
Assert.notNull(dbRefResolver, "DbRefResolver must not be null!");
return (source) -> {
if (targetType != Object.class && ClassUtils.isAssignable(targetType, source.getClass())) {
return (T) source;
}
if (targetType != Object.class && ClassUtils.isAssignable(targetType, source.getClass())) {
return (T) source;
}
if (source instanceof BsonValue) {
if (source instanceof BsonValue) {
Object value = BsonUtils.toJavaType((BsonValue) source);
Object value = BsonUtils.toJavaType((BsonValue) source);
if (value instanceof Document) {
if (value instanceof Document) {
Document sourceDocument = (Document) value;
Document sourceDocument = (Document) value;
if (sourceDocument.containsKey("$ref") && sourceDocument.containsKey("$id")) {
if (sourceDocument.containsKey("$ref") && sourceDocument.containsKey("$id")) {
sourceDocument = dbRefResolver
.fetch(new DBRef(sourceDocument.getString("$ref"), sourceDocument.get("$id")));
if (sourceDocument == null) {
return null;
}
}
return read(targetType, sourceDocument);
} else {
if (!ClassUtils.isAssignable(targetType, value.getClass())) {
if (getConversionService().canConvert(value.getClass(), targetType)) {
return getConversionService().convert(value, targetType);
}
sourceDocument = dbRefResolver.fetch(new DBRef(sourceDocument.getString("$ref"), sourceDocument.get("$id")));
if (sourceDocument == null) {
return null;
}
}
return (T) value;
return read(targetType, sourceDocument);
} else {
if (!ClassUtils.isAssignable(targetType, value.getClass())) {
if (getConversionService().canConvert(value.getClass(), targetType)) {
return getConversionService().convert(value, targetType);
}
}
}
return (T) getConversionService().convert(source, targetType);
};
return (T) value;
}
return getConversionService().convert(source, targetType);
}
}

View File

@@ -338,7 +338,7 @@ inline fun <reified T : Any> MongoOperations.findDistinct(field: String, entityC
* @since 2.1
*/
inline fun <reified T : Any> MongoOperations.findDistinct(query: Query, field: String, entityClass: KClass<*>): List<T> =
findDistinct(query, field, entityClass.java, T::class.java);
findDistinct(query, field, entityClass.java, T::class.java)
/**
* Extension for [MongoOperations.findDistinct] leveraging reified type parameters.
@@ -346,17 +346,19 @@ inline fun <reified T : Any> MongoOperations.findDistinct(query: Query, field: S
* @author Christoph Strobl
* @since 2.1
*/
inline fun <reified T : Any> MongoOperations.findDistinct(query: Query, field: String, collectionName: String?, entityClass: KClass<*>): List<T> =
findDistinct(query, field, collectionName, entityClass.java, T::class.java);
inline fun <reified T : Any> MongoOperations.findDistinct(query: Query, field: String, collectionName: String, entityClass: KClass<*>): List<T> =
findDistinct(query, field, collectionName, entityClass.java, T::class.java)
/**
* Extension for [MongoOperations.findDistinct] leveraging reified type parameters.
*
* @author Christoph Strobl
* @author Mark Paluch
* @since 2.1
*/
inline fun <reified T : Any> MongoOperations.findDistinct(query: Query, field: String, collectionName: String?): List<T> =
findDistinct(query, field, collectionName, T::class.java);
inline fun <reified T : Any, reified E : Any> MongoOperations.findDistinct(query: Query, field: String, collectionName: String? = null): List<T> =
if (collectionName != null) findDistinct(query, field, collectionName, E::class.java, T::class.java)
else findDistinct(query, field, E::class.java, T::class.java)
/**
* Extension for [MongoOperations.findAndModify] leveraging reified type parameters.

View File

@@ -109,7 +109,6 @@ fun <T : Any> ReactiveMongoOperations.dropCollection(entityClass: KClass<T>): Mo
inline fun <reified T : Any> ReactiveMongoOperations.dropCollection(): Mono<Void> =
dropCollection(T::class.java)
/**
* Extension for [ReactiveMongoOperations.findAll] leveraging reified type parameters.
*
@@ -166,7 +165,7 @@ inline fun <reified T : Any> ReactiveMongoOperations.findById(id: Any, collectio
if (collectionName != null) findById(id, T::class.java, collectionName) else findById(id, T::class.java)
/**
* Extension for [MongoOperations.findDistinct] leveraging reified type parameters.
* Extension for [ReactiveMongoOperations.findDistinct] leveraging reified type parameters.
*
* @author Christoph Strobl
* @since 2.1
@@ -175,31 +174,33 @@ inline fun <reified T : Any> ReactiveMongoOperations.findDistinct(field: String,
findDistinct(field, entityClass.java, T::class.java);
/**
* Extension for [MongoOperations.findDistinct] leveraging reified type parameters.
* Extension for [ReactiveMongoOperations.findDistinct] leveraging reified type parameters.
*
* @author Christoph Strobl
* @since 2.1
*/
inline fun <reified T : Any> ReactiveMongoOperations.findDistinct(query: Query, field: String, entityClass: KClass<*>): Flux<T> =
findDistinct(query, field, entityClass.java, T::class.java);
findDistinct(query, field, entityClass.java, T::class.java)
/**
* Extension for [MongoOperations.findDistinct] leveraging reified type parameters.
* Extension for [ReactiveMongoOperations.findDistinct] leveraging reified type parameters.
*
* @author Christoph Strobl
* @since 2.1
*/
inline fun <reified T : Any> ReactiveMongoOperations.findDistinct(query: Query, field: String, collectionName: String?, entityClass: KClass<*>): Flux<T> =
findDistinct(query, field, collectionName, entityClass.java, T::class.java);
inline fun <reified T : Any> ReactiveMongoOperations.findDistinct(query: Query, field: String, collectionName: String, entityClass: KClass<*>): Flux<T> =
findDistinct(query, field, collectionName, entityClass.java, T::class.java)
/**
* Extension for [MongoOperations.findDistinct] leveraging reified type parameters.
* Extension for [ReactiveMongoOperations.findDistinct] leveraging reified type parameters.
*
* @author Christoph Strobl
* @author Mark Paluch
* @since 2.1
*/
inline fun <reified T : Any> ReactiveMongoOperations.findDistinct(query: Query, field: String, collectionName: String?): Flux<T> =
findDistinct(query, field, collectionName, T::class.java);
inline fun <reified T : Any, reified E : Any> ReactiveMongoOperations.findDistinct(query: Query, field: String, collectionName: String? = null): Flux<T> =
if (collectionName != null) findDistinct(query, field, collectionName, E::class.java, T::class.java)
else findDistinct(query, field, E::class.java, T::class.java)
/**
* Extension for [ReactiveMongoOperations.geoNear] leveraging reified type parameters.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 the original author or authors.
* Copyright 2017-2018 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.
@@ -372,7 +372,7 @@ public class ExecutableFindOperationSupportTests {
}
@Test // DATAMONGO-1761
public void distinctReturnsSimpleFieldValuesCorrectlyForCollectionHavingNoReturnTypeSpecified() {
public void distinctReturnsSimpleFieldValuesCorrectly() {
Person anakin = new Person();
anakin.firstname = "anakin";
@@ -395,7 +395,7 @@ public class ExecutableFindOperationSupportTests {
}
@Test // DATAMONGO-1761
public void distinctReturnsComplexValuesCorrectlyForCollectionHavingNoReturnTypeSpecified() {
public void distinctReturnsComplexValuesCorrectly() {
Sith sith = new Sith();
sith.rank = "lord";
@@ -410,7 +410,7 @@ public class ExecutableFindOperationSupportTests {
}
@Test // DATAMONGO-1761
public void distinctReturnsComplexValuesCorrectlyForCollectionHavingReturnTypeSpecified() {
public void distinctReturnsComplexValuesCorrectlyHavingReturnTypeSpecified() {
Sith sith = new Sith();
sith.rank = "lord";
@@ -426,7 +426,7 @@ public class ExecutableFindOperationSupportTests {
}
@Test // DATAMONGO-1761
public void distinctReturnsComplexValuesCorrectlyForCollectionHavingReturnTypeDocumentSpecified() {
public void distinctReturnsComplexValuesCorrectlyHavingReturnTypeDocumentSpecified() {
Sith sith = new Sith();
sith.rank = "lord";

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 the original author or authors.
* Copyright 2017-2018 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.
@@ -114,15 +114,17 @@ public class ReactiveFindOperationSupportTests {
@Test // DATAMONGO-1719
public void findAllWithProjection() {
StepVerifier.create(template.query(Person.class).as(Jedi.class).all().map(it -> it.getClass().getName()))
.expectNext(Jedi.class.getName(), Jedi.class.getName()).verifyComplete();
StepVerifier.create(template.query(Person.class).as(Jedi.class).all().map(it -> it.getClass().getName())) //
.expectNext(Jedi.class.getName(), Jedi.class.getName()) //
.verifyComplete();
}
@Test // DATAMONGO-1719
public void findAllBy() {
StepVerifier.create(template.query(Person.class).matching(query(where("firstname").is("luke"))).all())
.expectNext(luke).verifyComplete();
StepVerifier.create(template.query(Person.class).matching(query(where("firstname").is("luke"))).all()) //
.expectNext(luke) //
.verifyComplete();
}
@Test // DATAMONGO-1719
@@ -130,7 +132,8 @@ public class ReactiveFindOperationSupportTests {
StepVerifier
.create(template.query(Jedi.class).inCollection(STAR_WARS).matching(query(where("name").is("luke"))).all())
.consumeNextWith(it -> assertThat(it).isInstanceOf(Jedi.class)).verifyComplete();
.consumeNextWith(it -> assertThat(it).isInstanceOf(Jedi.class)) //
.verifyComplete();
}
@Test // DATAMONGO-1719
@@ -139,7 +142,8 @@ public class ReactiveFindOperationSupportTests {
StepVerifier
.create(
template.query(Human.class).inCollection(STAR_WARS).matching(query(where("firstname").is("luke"))).all())
.expectNextCount(1).verifyComplete();
.expectNextCount(1) //
.verifyComplete();
}
@Test // DATAMONGO-1719
@@ -147,7 +151,8 @@ public class ReactiveFindOperationSupportTests {
StepVerifier
.create(template.query(Person.class).as(Jedi.class).matching(query(where("firstname").is("luke"))).all())
.consumeNextWith(it -> assertThat(it).isInstanceOf(Jedi.class)).verifyComplete();
.consumeNextWith(it -> assertThat(it).isInstanceOf(Jedi.class)) //
.verifyComplete();
}
@Test // DATAMONGO-1719
@@ -159,7 +164,8 @@ public class ReactiveFindOperationSupportTests {
assertThat(it).isInstanceOf(PersonProjection.class);
assertThat(it.getFirstname()).isEqualTo("luke");
}).verifyComplete();
}) //
.verifyComplete();
}
@Test // DATAMONGO-1719
@@ -170,14 +176,16 @@ public class ReactiveFindOperationSupportTests {
assertThat(it).isInstanceOf(PersonSpELProjection.class);
assertThat(it.getName()).isEqualTo("luke");
}).verifyComplete();
}) //
.verifyComplete();
}
@Test // DATAMONGO-1719
public void findBy() {
StepVerifier.create(template.query(Person.class).matching(query(where("firstname").is("luke"))).one())
.expectNext(luke).verifyComplete();
.expectNext(luke) //
.verifyComplete();
}
@Test // DATAMONGO-1719
@@ -191,7 +199,8 @@ public class ReactiveFindOperationSupportTests {
public void findByTooManyResults() {
StepVerifier.create(template.query(Person.class).matching(query(where("firstname").in("han", "luke"))).one())
.expectError(IncorrectResultSizeDataAccessException.class).verify();
.expectError(IncorrectResultSizeDataAccessException.class) //
.verify();
}
@Test // DATAMONGO-1719
@@ -209,7 +218,9 @@ public class ReactiveFindOperationSupportTests {
StepVerifier.create(template.query(Planet.class).near(NearQuery.near(-73.9667, 40.78).spherical(true)).all())
.consumeNextWith(actual -> {
assertThat(actual.getDistance()).isNotNull();
}).expectNextCount(1).verifyComplete();
}) //
.expectNextCount(1) //
.verifyComplete();
}
@Test // DATAMONGO-1719
@@ -229,7 +240,9 @@ public class ReactiveFindOperationSupportTests {
assertThat(actual.getDistance()).isNotNull();
assertThat(actual.getContent()).isInstanceOf(Human.class);
assertThat(actual.getContent().getId()).isEqualTo("alderan");
}).expectNextCount(1).verifyComplete();
}) //
.expectNextCount(1) //
.verifyComplete();
}
@Test // DATAMONGO-1719
@@ -250,7 +263,9 @@ public class ReactiveFindOperationSupportTests {
assertThat(it.getDistance()).isNotNull();
assertThat(it.getContent()).isInstanceOf(PlanetProjection.class);
assertThat(it.getContent().getName()).isEqualTo("alderan");
}).expectNextCount(1).verifyComplete();
}) //
.expectNextCount(1) //
.verifyComplete();
}
@Test // DATAMONGO-1719
@@ -271,7 +286,9 @@ public class ReactiveFindOperationSupportTests {
assertThat(it.getDistance()).isNotNull();
assertThat(it.getContent()).isInstanceOf(PlanetSpELProjection.class);
assertThat(it.getContent().getId()).isEqualTo("alderan");
}).expectNextCount(1).verifyComplete();
}) //
.expectNextCount(1) //
.verifyComplete();
}
@Test // DATAMONGO-1719
@@ -289,7 +306,8 @@ public class ReactiveFindOperationSupportTests {
StepVerifier
.create(template.query(Person.class).matching(query(where("firstname").is(luke.getFirstname()))).count())
.expectNext(1L).verifyComplete();
.expectNext(1L) //
.verifyComplete();
}
@Test // DATAMONGO-1719
@@ -310,14 +328,16 @@ public class ReactiveFindOperationSupportTests {
StepVerifier
.create(template.query(Person.class).matching(query(where("firstname").is(luke.getFirstname()))).exists())
.expectNext(true).verifyComplete();
.expectNext(true) //
.verifyComplete();
}
@Test // DATAMONGO-1719
public void existsShouldReturnFalseWhenNoElementMatchesQuery() {
StepVerifier.create(template.query(Person.class).matching(query(where("firstname").is("spock"))).exists())
.expectNext(false).verifyComplete();
.expectNext(false) //
.verifyComplete();
}
@Test // DATAMONGO-1761
@@ -337,11 +357,12 @@ public class ReactiveFindOperationSupportTests {
blocking.save(anakin);
StepVerifier.create(template.query(Person.class).distinct("lastname").as(String.class).all())
.assertNext(in("solo", "skywalker")).assertNext(in("solo", "skywalker")).verifyComplete();
.assertNext(in("solo", "skywalker")).assertNext(in("solo", "skywalker")) //
.verifyComplete();
}
@Test // DATAMONGO-1761
public void distinctReturnsSimpleFieldValuesCorrectlyForCollectionHavingNoReturnTypeSpecified() {
public void distinctReturnsSimpleFieldValuesCorrectly() {
Person anakin = new Person();
anakin.firstname = "anakin";
@@ -361,12 +382,15 @@ public class ReactiveFindOperationSupportTests {
Consumer<Object> containedInAbilities = in(anakin.ability, padme.ability, jaja.ability);
StepVerifier.create(template.query(Person.class).distinct("ability").all()).assertNext(containedInAbilities)
.assertNext(containedInAbilities).assertNext(containedInAbilities).verifyComplete();
StepVerifier.create(template.query(Person.class).distinct("ability").all()) //
.assertNext(containedInAbilities) //
.assertNext(containedInAbilities) //
.assertNext(containedInAbilities) //
.verifyComplete();
}
@Test // DATAMONGO-1761
public void distinctReturnsComplexValuesCorrectlyForCollectionHavingNoReturnTypeSpecified() {
public void distinctReturnsComplexValuesCorrectly() {
Sith sith = new Sith();
sith.rank = "lord";
@@ -377,12 +401,13 @@ public class ReactiveFindOperationSupportTests {
blocking.save(anakin);
StepVerifier.create(template.query(Person.class).distinct("ability").all()).expectNext(anakin.ability)
StepVerifier.create(template.query(Person.class).distinct("ability").all()) //
.expectNext(anakin.ability) //
.verifyComplete();
}
@Test // DATAMONGO-1761
public void distinctReturnsComplexValuesCorrectlyForCollectionHavingReturnTypeSpecified() {
public void distinctReturnsComplexValuesCorrectlyHavingReturnTypeSpecified() {
Sith sith = new Sith();
sith.rank = "lord";
@@ -393,12 +418,13 @@ public class ReactiveFindOperationSupportTests {
blocking.save(anakin);
StepVerifier.create(template.query(Person.class).distinct("ability").as(Sith.class).all()).expectNext(sith)
StepVerifier.create(template.query(Person.class).distinct("ability").as(Sith.class).all()) //
.expectNext(sith) //
.verifyComplete();
}
@Test // DATAMONGO-1761
public void distinctReturnsComplexValuesCorrectlyForCollectionHavingReturnTypeDocumentSpecified() {
public void distinctReturnsComplexValuesCorrectlyReturnTypeDocumentSpecified() {
Sith sith = new Sith();
sith.rank = "lord";
@@ -410,14 +436,16 @@ public class ReactiveFindOperationSupportTests {
blocking.save(anakin);
StepVerifier.create(template.query(Person.class).distinct("ability").as(Document.class).all())
.expectNext(new Document("rank", "lord").append("_class", Sith.class.getName())).verifyComplete();
.expectNext(new Document("rank", "lord").append("_class", Sith.class.getName())) //
.verifyComplete();
}
@Test // DATAMONGO-1761
public void distinctMapsFieldNameCorrectly() {
StepVerifier.create(template.query(Jedi.class).inCollection(STAR_WARS).distinct("name").as(String.class).all())
.assertNext(in("han", "luke")).assertNext(in("han", "luke")).verifyComplete();
.assertNext(in("han", "luke")).assertNext(in("han", "luke")) //
.verifyComplete();
}
@Test // DATAMONGO-1761
@@ -425,7 +453,9 @@ public class ReactiveFindOperationSupportTests {
Consumer<BsonValue> inValues = in(new BsonString("solo"), new BsonString("skywalker"));
StepVerifier.create(template.query(Person.class).distinct("lastname").as(BsonValue.class).all())
.assertNext(inValues).assertNext(inValues).verifyComplete();
.assertNext(inValues) //
.assertNext(inValues) //
.verifyComplete();
}
@Test // DATAMONGO-1761
@@ -433,7 +463,9 @@ public class ReactiveFindOperationSupportTests {
blocking.save(new Document("darth", "vader"), STAR_WARS);
StepVerifier.create(template.query(Person.class).distinct("darth").all()).expectNext("vader").verifyComplete();
StepVerifier.create(template.query(Person.class).distinct("darth").all()) //
.expectNext("vader") //
.verifyComplete();
}
@Test // DATAMONGO-1761
@@ -445,7 +477,8 @@ public class ReactiveFindOperationSupportTests {
blocking.save(luke);
StepVerifier.create(template.query(Person.class).distinct("father").as(Jedi.class).all())
.expectNext(new Jedi("anakin")).verifyComplete();
.expectNext(new Jedi("anakin")) //
.verifyComplete();
}
@Test // DATAMONGO-1761
@@ -457,7 +490,8 @@ public class ReactiveFindOperationSupportTests {
blocking.save(luke);
StepVerifier.create(template.query(Object.class).inCollection(STAR_WARS).distinct("father").as(Jedi.class).all())
.expectNext(new Jedi("anakin")).verifyComplete();
.expectNext(new Jedi("anakin")) //
.verifyComplete();
}
@Test // DATAMONGO-1761
@@ -471,13 +505,17 @@ public class ReactiveFindOperationSupportTests {
Person expected = new Person();
expected.firstname = luke.father.firstname;
StepVerifier.create(template.query(Person.class).distinct("father").all()).expectNext(expected).verifyComplete();
StepVerifier.create(template.query(Person.class).distinct("father").all()) //
.expectNext(expected) //
.verifyComplete();
}
@Test // DATAMONGO-1761
public void distinctThrowsExceptionWhenExplicitMappingTypeCannotBeApplied() {
StepVerifier.create(template.query(Person.class).distinct("firstname").as(Long.class).all())
.expectError(InvalidDataAccessApiUsageException.class).verify();
.expectError(InvalidDataAccessApiUsageException.class) //
.verify();
}
interface Contact {}

View File

@@ -36,6 +36,7 @@ import org.springframework.data.mongodb.core.query.Update
/**
* @author Sebastien Deleuze
* @author Mark Paluch
* @author Christoph Strobl
*/
@RunWith(MockitoJUnitRunner::class)
class MongoOperationsExtensionsTests {
@@ -704,12 +705,30 @@ class MongoOperationsExtensionsTests {
verify(operations).findDistinct(query, "field", "collection", First::class.java, String::class.java)
}
@Test // DATAMONGO-1761
fun `findDistinctImplicit(Query, String) should call java counterpart`() {
val query = mock<Query>()
operations.findDistinct<String, First>(query, "field")
verify(operations).findDistinct(query, "field", First::class.java, String::class.java)
}
@Test // DATAMONGO-1761
fun `findDistinct(Query, String, String) should call java counterpart`() {
val query = mock<Query>()
operations.findDistinct<String>(query, "field", "collection")
verify(operations).findDistinct(query, "field", "collection", String::class.java)
operations.findDistinct<String, First>(query, "field", "collection")
verify(operations).findDistinct(query, "field", "collection", First::class.java, String::class.java)
}
@Test // DATAMONGO-1761
fun `findDistinct(Query, String, KClass) should call java counterpart`() {
val query = mock<Query>()
operations.findDistinct<String>(query, "field", First::class)
verify(operations).findDistinct(query, "field", First::class.java, String::class.java)
}
}

View File

@@ -30,6 +30,8 @@ import reactor.core.publisher.Mono
/**
* @author Sebastien Deleuze
* @author Christoph Strobl
* @author Mark Paluch
*/
@RunWith(MockitoJUnitRunner::class)
class ReactiveMongoOperationsExtensionsTests {
@@ -552,12 +554,31 @@ class ReactiveMongoOperationsExtensionsTests {
verify(operations).findDistinct(query, "field", "collection", First::class.java, String::class.java)
}
@Test // DATAMONGO-1761
fun `findDistinctImplicit(Query, String) should call java counterpart`() {
val query = mock<Query>()
operations.findDistinct<String, First>(query, "field")
verify(operations).findDistinct(query, "field", First::class.java, String::class.java)
}
@Test // DATAMONGO-1761
fun `findDistinct(Query, String, String) should call java counterpart`() {
val query = mock<Query>()
operations.findDistinct<String>(query, "field", "collection")
verify(operations).findDistinct(query, "field", "collection", String::class.java)
operations.findDistinct<String, First>(query, "field", "collection")
verify(operations).findDistinct(query, "field", "collection", First::class.java, String::class.java)
}
@Test // DATAMONGO-1761
fun `findDistinct(Query, String, KClass) should call java counterpart`() {
val query = mock<Query>()
operations.findDistinct<String>(query, "field", First::class)
verify(operations).findDistinct(query, "field", First::class.java, String::class.java)
}
}

View File

@@ -4,6 +4,7 @@
[[new-features.2-1-0]]
== What's new in Spring Data MongoDB 2.1
* Cursor-based aggregation execution.
* <<mongo-template.query.distinct,Distinct queries>> for imperative and reactive Template API.
[[new-features.2-0-0]]
== What's new in Spring Data MongoDB 2.0

View File

@@ -1090,8 +1090,9 @@ The query methods need to specify the target type T that will be returned and th
[[mongo-template.query.distinct]]
=== Query distinct values
MongoDB allows obtaining distinct field values for a single field. The stored values do not have to have the same data type to be considered, nor is the feature limited to simple types.
However when retrieving distinct values the actual result type does matter for the sake of conversion.
MongoDB provides an operation to obtain distinct values for a single field using a query from the resulting documents.
Resulting values are not required to have the same data type, nor is the feature limited to simple types.
For retriaval the actual result type does matter for the sake of conversion and typing.
.Retrieving distinct values
====
@@ -1100,13 +1101,13 @@ However when retrieving distinct values the actual result type does matter for t
template.query(Person.class) <1>
.distinct("lastname") <2>
.all(); <3>
---
----
<1> Query the collection of `Person`.
<2> Select _distinct_ values of the `lastname` field. The fieldname will be mapped according to the domain types property declaration, taking potential `@Field` annotations into account.
<3> Retrieve all distinct values as `List` of `Object` due to no explicit result type specification.
====
Retrieving distinct values into a `Collection` of `Object.class` is the most flexible way as it will try to determine the property value of the domain type converting results to the desired type or mapping `Document` structures.
Retrieving distinct values into a `Collection` of `Object` is the most flexible way as it will try to determine the property value of the domain type converting results to the desired type or mapping `Document` structures.
Sometimes, when all values of the desired field are fixed to a certain type, it is more convenient to directly obtain a correctly typed `Collection`
@@ -1118,12 +1119,12 @@ template.query(Person.class) <1>
.distinct("lastname") <2>
.as(String.class) <3>
.all(); <4>
---
----
<1> Query the collection of `Person`.
<2> Select _distinct_ values of the `lastname` field. The fieldname will be mapped according to the domain types property declaration, taking potential `@Field` annotations into account.
<3> Retrieved values will be converted into the desired target type. In this case `String`. It would also be possible to map the values to a more complex type if the stored field contains a document.
<4> Retrieve all distinct values as a `List` of `String`. Throws a `DataAccessException` if the type cannot be converted into the desired target type.
===
====
[[mongo.geospatial]]
=== GeoSpatial Queries