Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12b4aab834 | ||
|
|
db06756c8f | ||
|
|
b319b8a589 | ||
|
|
a516795759 | ||
|
|
bab08502a5 | ||
|
|
3e1f95bc94 | ||
|
|
5c153dc76e | ||
|
|
8f4e207d97 | ||
|
|
5000a40d72 | ||
|
|
fb59f49dae | ||
|
|
f3c1e014e9 | ||
|
|
f52cc3be1f | ||
|
|
1bda93858c | ||
|
|
1808970daf | ||
|
|
558fc28cce | ||
|
|
16bef54f11 | ||
|
|
d68a812e1b | ||
|
|
ccb9f111d9 | ||
|
|
f64b177c8f | ||
|
|
c0c7ba767f | ||
|
|
7639701f3f | ||
|
|
b39b2591b6 | ||
|
|
65c8317e38 | ||
|
|
9d0f7bac6a | ||
|
|
6f50747d21 | ||
|
|
5cf1578ad3 | ||
|
|
78a59c45ca | ||
|
|
dccdfc8b4d | ||
|
|
e48239eb8f | ||
|
|
c3b4f61d29 | ||
|
|
22ed860b4a | ||
|
|
bf642ad3f7 | ||
|
|
fcd48539ea | ||
|
|
bf10f72a57 | ||
|
|
1c652cce1c | ||
|
|
dc2de878bc | ||
|
|
00cacc02ac | ||
|
|
811c2e5d7b | ||
|
|
200f3006bd | ||
|
|
1d6bea51ec |
8
pom.xml
8
pom.xml
@@ -5,7 +5,7 @@
|
||||
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>3.1.5</version>
|
||||
<version>3.1.8</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>Spring Data MongoDB</name>
|
||||
@@ -15,7 +15,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data.build</groupId>
|
||||
<artifactId>spring-data-parent</artifactId>
|
||||
<version>2.4.5</version>
|
||||
<version>2.4.8</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
@@ -26,8 +26,8 @@
|
||||
<properties>
|
||||
<project.type>multi</project.type>
|
||||
<dist.id>spring-data-mongodb</dist.id>
|
||||
<springdata.commons>2.4.5</springdata.commons>
|
||||
<mongo>4.1.1</mongo>
|
||||
<springdata.commons>2.4.8</springdata.commons>
|
||||
<mongo>4.1.2</mongo>
|
||||
<mongo.reactivestreams>${mongo}</mongo.reactivestreams>
|
||||
<jmh.version>1.19</jmh.version>
|
||||
</properties>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>3.1.5</version>
|
||||
<version>3.1.8</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>3.1.5</version>
|
||||
<version>3.1.8</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>3.1.5</version>
|
||||
<version>3.1.8</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.type.filter.AnnotationTypeFilter;
|
||||
import org.springframework.data.annotation.Persistent;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.mapping.model.CamelCaseAbbreviatingFieldNamingStrategy;
|
||||
import org.springframework.data.mapping.model.FieldNamingStrategy;
|
||||
@@ -140,8 +139,7 @@ public abstract class MongoConfigurationSupport {
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans the given base package for entities, i.e. MongoDB specific types annotated with {@link Document} and
|
||||
* {@link Persistent}.
|
||||
* Scans the given base package for entities, i.e. MongoDB specific types annotated with {@link Document}.
|
||||
*
|
||||
* @param basePackage must not be {@literal null}.
|
||||
* @return
|
||||
@@ -161,7 +159,6 @@ public abstract class MongoConfigurationSupport {
|
||||
ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider(
|
||||
false);
|
||||
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Document.class));
|
||||
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class));
|
||||
|
||||
for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) {
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bson.BsonInvalidOperationException;
|
||||
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.DataAccessResourceFailureException;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
@@ -39,6 +40,7 @@ import org.springframework.util.ClassUtils;
|
||||
import com.mongodb.MongoBulkWriteException;
|
||||
import com.mongodb.MongoException;
|
||||
import com.mongodb.MongoServerException;
|
||||
import com.mongodb.MongoSocketException;
|
||||
import com.mongodb.bulk.BulkWriteError;
|
||||
|
||||
/**
|
||||
@@ -49,6 +51,7 @@ import com.mongodb.bulk.BulkWriteError;
|
||||
* @author Oliver Gierke
|
||||
* @author Michal Vich
|
||||
* @author Christoph Strobl
|
||||
* @author Brice Vandeputte
|
||||
*/
|
||||
public class MongoExceptionTranslator implements PersistenceExceptionTranslator {
|
||||
|
||||
@@ -78,6 +81,10 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator
|
||||
throw new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
if (ex instanceof MongoSocketException) {
|
||||
return new DataAccessResourceFailureException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
String exception = ClassUtils.getShortName(ClassUtils.getUserClass(ex.getClass()));
|
||||
|
||||
if (DUPLICATE_KEY_EXCEPTIONS.contains(exception)) {
|
||||
|
||||
@@ -658,7 +658,8 @@ class QueryOperations {
|
||||
: mappedDocument != null ? mappedDocument.getDocument() : getMappedUpdate(domainType);
|
||||
|
||||
Document filterWithShardKey = new Document(filter);
|
||||
getMappedShardKeyFields(domainType).forEach(key -> filterWithShardKey.putIfAbsent(key, shardKeySource.get(key)));
|
||||
getMappedShardKeyFields(domainType)
|
||||
.forEach(key -> filterWithShardKey.putIfAbsent(key, BsonUtils.resolveValue(shardKeySource, key)));
|
||||
|
||||
return filterWithShardKey;
|
||||
}
|
||||
|
||||
@@ -17,17 +17,16 @@ package org.springframework.data.mongodb.core.convert;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.bson.conversions.Bson;
|
||||
|
||||
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 com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
@@ -110,28 +109,7 @@ class DocumentAccessor {
|
||||
*/
|
||||
@Nullable
|
||||
public Object get(MongoPersistentProperty property) {
|
||||
|
||||
String fieldName = property.getFieldName();
|
||||
Map<String, Object> map = BsonUtils.asMap(document);
|
||||
|
||||
if (!fieldName.contains(".")) {
|
||||
return map.get(fieldName);
|
||||
}
|
||||
|
||||
Iterator<String> parts = Arrays.asList(fieldName.split("\\.")).iterator();
|
||||
Map<String, Object> source = map;
|
||||
Object result = null;
|
||||
|
||||
while (source != null && parts.hasNext()) {
|
||||
|
||||
result = source.get(parts.next());
|
||||
|
||||
if (parts.hasNext()) {
|
||||
source = getAsMap(result);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return BsonUtils.resolveValue(document, property.getFieldName());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,71 +135,7 @@ class DocumentAccessor {
|
||||
|
||||
Assert.notNull(property, "Property must not be null!");
|
||||
|
||||
String fieldName = property.getFieldName();
|
||||
|
||||
|
||||
if (this.document instanceof Document) {
|
||||
|
||||
if (((Document) this.document).containsKey(fieldName)) {
|
||||
return true;
|
||||
}
|
||||
} else if (this.document instanceof DBObject) {
|
||||
if (((DBObject) this.document).containsField(fieldName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!fieldName.contains(".")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String[] parts = fieldName.split("\\.");
|
||||
Map<String, Object> source;
|
||||
|
||||
if (this.document instanceof Document) {
|
||||
source = ((Document) this.document);
|
||||
} else {
|
||||
source = ((DBObject) this.document).toMap();
|
||||
}
|
||||
|
||||
Object result = null;
|
||||
|
||||
for (int i = 1; i < parts.length; i++) {
|
||||
|
||||
result = source.get(parts[i - 1]);
|
||||
source = getAsMap(result);
|
||||
|
||||
if (source == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return source.containsKey(parts[parts.length - 1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given source object as map, i.e. {@link Document}s and maps as is or {@literal null} otherwise.
|
||||
*
|
||||
* @param source can be {@literal null}.
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Map<String, Object> getAsMap(Object source) {
|
||||
|
||||
if (source instanceof Document) {
|
||||
return (Document) source;
|
||||
}
|
||||
|
||||
if (source instanceof BasicDBObject) {
|
||||
return (BasicDBObject) source;
|
||||
}
|
||||
|
||||
if (source instanceof Map) {
|
||||
return (Map<String, Object>) source;
|
||||
}
|
||||
|
||||
return null;
|
||||
return BsonUtils.hasValue(document, property.getFieldName());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -75,6 +75,7 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.BasicDBList;
|
||||
import com.mongodb.BasicDBObject;
|
||||
@@ -182,6 +183,9 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
* any translation but rather reject a {@link Map} with keys containing dots causing the conversion for the entire
|
||||
* object to fail. If further customization of the translation is needed, have a look at
|
||||
* {@link #potentiallyEscapeMapKey(String)} as well as {@link #potentiallyUnescapeMapKey(String)}.
|
||||
* <p>
|
||||
* {@code mapKeyDotReplacement} is used as-is during replacement operations without further processing (i.e. regex or
|
||||
* normalization).
|
||||
*
|
||||
* @param mapKeyDotReplacement the mapKeyDotReplacement to set. Can be {@literal null}.
|
||||
*/
|
||||
@@ -900,7 +904,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
source));
|
||||
}
|
||||
|
||||
return source.replaceAll("\\.", mapKeyDotReplacement);
|
||||
return StringUtils.replace(source, ".", mapKeyDotReplacement);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -928,7 +932,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
* @return
|
||||
*/
|
||||
protected String potentiallyUnescapeMapKey(String source) {
|
||||
return mapKeyDotReplacement == null ? source : source.replaceAll(mapKeyDotReplacement, "\\.");
|
||||
return mapKeyDotReplacement == null ? source : StringUtils.replace(source, mapKeyDotReplacement, ".");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1086,8 +1086,8 @@ public class QueryMapper {
|
||||
removePlaceholders(DOT_POSITIONAL_PATTERN, pathExpression));
|
||||
|
||||
if (sourceProperty != null && sourceProperty.getOwner().equals(entity)) {
|
||||
return mappingContext
|
||||
.getPersistentPropertyPath(PropertyPath.from(sourceProperty.getName(), entity.getTypeInformation()));
|
||||
return mappingContext.getPersistentPropertyPath(
|
||||
PropertyPath.from(Pattern.quote(sourceProperty.getName()), entity.getTypeInformation()));
|
||||
}
|
||||
|
||||
PropertyPath path = forName(rawPath);
|
||||
@@ -1146,13 +1146,21 @@ public class QueryMapper {
|
||||
return forName(path.substring(0, path.length() - 3) + "id");
|
||||
}
|
||||
|
||||
// Ok give it another try quoting
|
||||
try {
|
||||
return PropertyPath.from(Pattern.quote(path), entity.getTypeInformation());
|
||||
} catch (PropertyReferenceException | InvalidPersistentPropertyPath ex) {
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPathToJavaLangClassProperty(PropertyPath path) {
|
||||
|
||||
if (path.getType().equals(Class.class) && path.getLeafProperty().getOwningType().getType().equals(Class.class)) {
|
||||
if ((path.getType() == Class.class || path.getType().equals(Object.class))
|
||||
&& path.getLeafProperty().getType() == Class.class) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -1261,9 +1269,9 @@ public class QueryMapper {
|
||||
|
||||
String partial = iterator.next();
|
||||
|
||||
boolean isPositional = (isPositionalParameter(partial) && (property.isMap() || property.isCollectionLike()));
|
||||
boolean isPositional = isPositionalParameter(partial) && property.isCollectionLike();
|
||||
|
||||
if (isPositional) {
|
||||
if (isPositional || property.isMap()) {
|
||||
mappedName.append(".").append(partial);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,10 @@ import static org.springframework.util.ObjectUtils.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -58,6 +60,7 @@ import com.mongodb.BasicDBList;
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Andreas Zink
|
||||
* @author Clément Petit
|
||||
*/
|
||||
public class Criteria implements CriteriaDefinition {
|
||||
|
||||
@@ -895,15 +898,15 @@ public class Criteria implements CriteriaDefinition {
|
||||
* @param right
|
||||
* @return
|
||||
*/
|
||||
private boolean isEqual(Object left, Object right) {
|
||||
private boolean isEqual(@Nullable Object left, @Nullable Object right) {
|
||||
|
||||
if (left == null) {
|
||||
return right == null;
|
||||
}
|
||||
|
||||
if (Pattern.class.isInstance(left)) {
|
||||
if (left instanceof Pattern) {
|
||||
|
||||
if (!Pattern.class.isInstance(right)) {
|
||||
if (!(right instanceof Pattern)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -914,6 +917,52 @@ public class Criteria implements CriteriaDefinition {
|
||||
&& leftPattern.flags() == rightPattern.flags();
|
||||
}
|
||||
|
||||
if (left instanceof Document) {
|
||||
|
||||
if (!(right instanceof Document)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Document leftDocument = (Document) left;
|
||||
Document rightDocument = (Document) right;
|
||||
Iterator<Entry<String, Object>> leftIterator = leftDocument.entrySet().iterator();
|
||||
Iterator<Entry<String, Object>> rightIterator = rightDocument.entrySet().iterator();
|
||||
|
||||
while (leftIterator.hasNext() && rightIterator.hasNext()) {
|
||||
|
||||
Map.Entry<String, Object> leftEntry = leftIterator.next();
|
||||
Map.Entry<String, Object> rightEntry = rightIterator.next();
|
||||
|
||||
if (!isEqual(leftEntry.getKey(), rightEntry.getKey())
|
||||
|| !isEqual(leftEntry.getValue(), rightEntry.getValue())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return !leftIterator.hasNext() && !rightIterator.hasNext();
|
||||
}
|
||||
|
||||
if (Collection.class.isAssignableFrom(left.getClass())) {
|
||||
|
||||
if (!Collection.class.isAssignableFrom(right.getClass())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Collection<?> leftCollection = (Collection<?>) left;
|
||||
Collection<?> rightCollection = (Collection<?>) right;
|
||||
Iterator<?> leftIterator = leftCollection.iterator();
|
||||
Iterator<?> rightIterator = rightCollection.iterator();
|
||||
|
||||
while (leftIterator.hasNext() && rightIterator.hasNext()) {
|
||||
|
||||
if (!isEqual(leftIterator.next(), rightIterator.next())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return !leftIterator.hasNext() && !rightIterator.hasNext();
|
||||
}
|
||||
|
||||
return ObjectUtils.nullSafeEquals(left, right);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.data.mongodb.core.query;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.bson.BsonRegularExpression;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@@ -102,6 +103,15 @@ public enum MongoRegexCreator {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param source
|
||||
* @return
|
||||
* @since 2.2.14
|
||||
*/
|
||||
public Object toCaseInsensitiveMatch(Object source) {
|
||||
return source instanceof String ? new BsonRegularExpression(Pattern.quote((String) source), "i") : source;
|
||||
}
|
||||
|
||||
private String prepareAndEscapeStringBeforeApplyingLikeRegex(String source, MatchMode matcherType) {
|
||||
|
||||
if (MatchMode.REGEX == matcherType) {
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.springframework.data.repository.query.QueryMethodEvaluationContextPro
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -163,9 +164,9 @@ abstract class AggregationUtils {
|
||||
* @throws IllegalArgumentException when none of the above rules is met.
|
||||
*/
|
||||
@Nullable
|
||||
static <T> T extractSimpleTypeResult(Document source, Class<T> targetType, MongoConverter converter) {
|
||||
static <T> T extractSimpleTypeResult(@Nullable Document source, Class<T> targetType, MongoConverter converter) {
|
||||
|
||||
if (source.isEmpty()) {
|
||||
if (ObjectUtils.isEmpty(source)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ import java.util.regex.Pattern;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.data.domain.Range;
|
||||
import org.springframework.data.domain.Range.Bound;
|
||||
import org.springframework.data.domain.Sort;
|
||||
@@ -51,8 +50,10 @@ import org.springframework.data.repository.query.parser.Part;
|
||||
import org.springframework.data.repository.query.parser.Part.IgnoreCaseType;
|
||||
import org.springframework.data.repository.query.parser.Part.Type;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Custom query creator to create Mongo criterias.
|
||||
@@ -196,9 +197,9 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
|
||||
case IS_NULL:
|
||||
return criteria.is(null);
|
||||
case NOT_IN:
|
||||
return criteria.nin(nextAsArray(parameters));
|
||||
return criteria.nin(nextAsList(parameters, part));
|
||||
case IN:
|
||||
return criteria.in(nextAsArray(parameters));
|
||||
return criteria.in(nextAsList(parameters, part));
|
||||
case LIKE:
|
||||
case STARTING_WITH:
|
||||
case ENDING_WITH:
|
||||
@@ -337,7 +338,7 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
|
||||
Iterator<Object> parameters) {
|
||||
|
||||
if (property.isCollectionLike()) {
|
||||
return criteria.in(nextAsArray(parameters));
|
||||
return criteria.in(nextAsList(parameters, part));
|
||||
}
|
||||
|
||||
return addAppropriateLikeRegexTo(criteria, part, parameters.next());
|
||||
@@ -400,17 +401,24 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
|
||||
String.format("Expected parameter type of %s but got %s!", type, parameter.getClass()));
|
||||
}
|
||||
|
||||
private Object[] nextAsArray(Iterator<Object> iterator) {
|
||||
private java.util.List<?> nextAsList(Iterator<Object> iterator, Part part) {
|
||||
|
||||
Object next = iterator.next();
|
||||
|
||||
if (next instanceof Collection) {
|
||||
return ((Collection<?>) next).toArray();
|
||||
} else if (next != null && next.getClass().isArray()) {
|
||||
return (Object[]) next;
|
||||
Streamable<?> streamable = asStreamable(iterator.next());
|
||||
if (!isSimpleComparisionPossible(part)) {
|
||||
streamable = streamable.map(MongoRegexCreator.INSTANCE::toCaseInsensitiveMatch);
|
||||
}
|
||||
|
||||
return new Object[] { next };
|
||||
return streamable.toList();
|
||||
}
|
||||
|
||||
private Streamable<?> asStreamable(Object value) {
|
||||
|
||||
if (value instanceof Collection) {
|
||||
return Streamable.of((Collection<?>) value);
|
||||
} else if (ObjectUtils.isArray(value)) {
|
||||
return Streamable.of((Object[]) value);
|
||||
}
|
||||
return Streamable.of(value);
|
||||
}
|
||||
|
||||
private String toLikeRegex(String source, Part part) {
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.data.mongodb.repository.support;
|
||||
import static org.springframework.data.mongodb.core.query.Criteria.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -215,7 +216,7 @@ public class SimpleMongoRepository<T, ID> implements MongoRepository<T, ID> {
|
||||
Assert.notNull(ids, "The given Ids of entities not be null!");
|
||||
|
||||
return findAll(new Query(new Criteria(entityInformation.getIdAttribute())
|
||||
.in(Streamable.of(ids).stream().collect(StreamUtils.toUnmodifiableList()))));
|
||||
.in(toCollection(ids))));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -266,10 +267,10 @@ public class SimpleMongoRepository<T, ID> implements MongoRepository<T, ID> {
|
||||
|
||||
Assert.notNull(entities, "The given Iterable of entities not be null!");
|
||||
|
||||
List<S> list = Streamable.of(entities).stream().collect(StreamUtils.toUnmodifiableList());
|
||||
Collection<S> list = toCollection(entities);
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return list;
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return new ArrayList<>(mongoOperations.insertAll(list));
|
||||
@@ -374,6 +375,11 @@ public class SimpleMongoRepository<T, ID> implements MongoRepository<T, ID> {
|
||||
return where(entityInformation.getIdAttribute()).is(id);
|
||||
}
|
||||
|
||||
private static <E> Collection<E> toCollection(Iterable<E> ids) {
|
||||
return ids instanceof Collection ? (Collection<E>) ids
|
||||
: StreamUtils.createStreamFromIterator(ids.iterator()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<T> findAll(@Nullable Query query) {
|
||||
|
||||
if (query == null) {
|
||||
|
||||
@@ -21,10 +21,11 @@ import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.data.domain.Example;
|
||||
@@ -47,6 +48,7 @@ import com.mongodb.client.result.DeleteResult;
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
* @author Ruben J Garcia
|
||||
* @author Clément Petit
|
||||
* @since 2.0
|
||||
*/
|
||||
public class SimpleReactiveMongoRepository<T, ID extends Serializable> implements ReactiveMongoRepository<T, ID> {
|
||||
@@ -173,7 +175,7 @@ public class SimpleReactiveMongoRepository<T, ID extends Serializable> implement
|
||||
Assert.notNull(ids, "The given Iterable of Id's must not be null!");
|
||||
|
||||
return findAll(new Query(new Criteria(entityInformation.getIdAttribute())
|
||||
.in(Streamable.of(ids).stream().collect(StreamUtils.toUnmodifiableList()))));
|
||||
.in(toCollection(ids))));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -274,9 +276,9 @@ public class SimpleReactiveMongoRepository<T, ID extends Serializable> implement
|
||||
|
||||
Assert.notNull(entities, "The given Iterable of entities must not be null!");
|
||||
|
||||
List<S> source = Streamable.of(entities).stream().collect(StreamUtils.toUnmodifiableList());
|
||||
Collection<S> source = toCollection(entities);
|
||||
|
||||
return source.isEmpty() ? Flux.empty() : Flux.from(mongoOperations.insertAll(source));
|
||||
return source.isEmpty() ? Flux.empty() : mongoOperations.insertAll(source);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -333,8 +335,8 @@ public class SimpleReactiveMongoRepository<T, ID extends Serializable> implement
|
||||
Assert.notNull(entityStream, "The given Publisher of entities must not be null!");
|
||||
|
||||
return Flux.from(entityStream).flatMap(entity -> entityInformation.isNew(entity) ? //
|
||||
mongoOperations.insert(entity, entityInformation.getCollectionName()).then(Mono.just(entity)) : //
|
||||
mongoOperations.save(entity, entityInformation.getCollectionName()).then(Mono.just(entity)));
|
||||
mongoOperations.insert(entity, entityInformation.getCollectionName()) : //
|
||||
mongoOperations.save(entity, entityInformation.getCollectionName()));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -436,8 +438,12 @@ public class SimpleReactiveMongoRepository<T, ID extends Serializable> implement
|
||||
return where(entityInformation.getIdAttribute()).is(id);
|
||||
}
|
||||
|
||||
private Flux<T> findAll(Query query) {
|
||||
private static <E> Collection<E> toCollection(Iterable<E> ids) {
|
||||
return ids instanceof Collection ? (Collection<E>) ids
|
||||
: StreamUtils.createStreamFromIterator(ids.iterator()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Flux<T> findAll(Query query) {
|
||||
return mongoOperations.find(query, entityInformation.getJavaType(), entityInformation.getCollectionName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,6 +282,109 @@ public class BsonUtils {
|
||||
.orElseGet(() -> new DocumentCodec(codecRegistryProvider.getCodecRegistry())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a the value for a given key. If the given {@link Bson} value contains the key the value is immediately
|
||||
* returned. If not and the key contains a path using the dot ({@code .}) notation it will try to resolve the path by
|
||||
* inspecting the individual parts. If one of the intermediate ones is {@literal null} or cannot be inspected further
|
||||
* (wrong) type, {@literal null} is returned.
|
||||
*
|
||||
* @param bson the source to inspect. Must not be {@literal null}.
|
||||
* @param key the key to lookup. Must not be {@literal null}.
|
||||
* @return can be {@literal null}.
|
||||
* @since 3.0.8
|
||||
*/
|
||||
@Nullable
|
||||
public static Object resolveValue(Bson bson, String key) {
|
||||
|
||||
Map<String, Object> source = asMap(bson);
|
||||
|
||||
if (source.containsKey(key) || !key.contains(".")) {
|
||||
return source.get(key);
|
||||
}
|
||||
|
||||
String[] parts = key.split("\\.");
|
||||
|
||||
for (int i = 1; i < parts.length; i++) {
|
||||
|
||||
Object result = source.get(parts[i - 1]);
|
||||
|
||||
if (!(result instanceof Bson)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
source = asMap((Bson) result);
|
||||
}
|
||||
|
||||
return source.get(parts[parts.length - 1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the underlying {@link Bson bson} has a value ({@literal null} or non-{@literal null}) for the given
|
||||
* {@code key}.
|
||||
*
|
||||
* @param bson the source to inspect. Must not be {@literal null}.
|
||||
* @param key the key to lookup. Must not be {@literal null}.
|
||||
* @return {@literal true} if no non {@literal null} value present.
|
||||
* @since 3.0.8
|
||||
*/
|
||||
public static boolean hasValue(Bson bson, String key) {
|
||||
|
||||
Map<String, Object> source = asMap(bson);
|
||||
|
||||
if (source.get(key) != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!key.contains(".")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String[] parts = key.split("\\.");
|
||||
|
||||
Object result;
|
||||
|
||||
for (int i = 1; i < parts.length; i++) {
|
||||
|
||||
result = source.get(parts[i - 1]);
|
||||
source = getAsMap(result);
|
||||
|
||||
if (source == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return source.containsKey(parts[parts.length - 1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given source object as map, i.e. {@link Document}s and maps as is or {@literal null} otherwise.
|
||||
*
|
||||
* @param source can be {@literal null}.
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Map<String, Object> getAsMap(Object source) {
|
||||
|
||||
if (source instanceof Document) {
|
||||
return (Document) source;
|
||||
}
|
||||
|
||||
if (source instanceof BasicDBObject) {
|
||||
return (BasicDBObject) source;
|
||||
}
|
||||
|
||||
if (source instanceof DBObject) {
|
||||
return ((DBObject) source).toMap();
|
||||
}
|
||||
|
||||
if (source instanceof Map) {
|
||||
return (Map<String, Object>) source;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String toJson(@Nullable Object value) {
|
||||
|
||||
|
||||
@@ -17,8 +17,6 @@ package org.springframework.data.mongodb.core;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import org.bson.BsonDocument;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -32,11 +30,14 @@ import org.springframework.dao.InvalidDataAccessResourceUsageException;
|
||||
import org.springframework.data.mongodb.ClientSessionException;
|
||||
import org.springframework.data.mongodb.MongoTransactionException;
|
||||
import org.springframework.data.mongodb.UncategorizedMongoDbException;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import com.mongodb.MongoCursorNotFoundException;
|
||||
import com.mongodb.MongoException;
|
||||
import com.mongodb.MongoInternalException;
|
||||
import com.mongodb.MongoSocketException;
|
||||
import com.mongodb.MongoSocketReadTimeoutException;
|
||||
import com.mongodb.MongoSocketWriteException;
|
||||
import com.mongodb.ServerAddress;
|
||||
|
||||
/**
|
||||
@@ -45,18 +46,20 @@ import com.mongodb.ServerAddress;
|
||||
* @author Michal Vich
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
* @author Brice Vandeputte
|
||||
*/
|
||||
public class MongoExceptionTranslatorUnitTests {
|
||||
class MongoExceptionTranslatorUnitTests {
|
||||
|
||||
MongoExceptionTranslator translator;
|
||||
private static final String EXCEPTION_MESSAGE = "IOException";
|
||||
private MongoExceptionTranslator translator;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
void setUp() {
|
||||
translator = new MongoExceptionTranslator();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void translateDuplicateKey() {
|
||||
void translateDuplicateKey() {
|
||||
|
||||
expectExceptionWithCauseMessage(
|
||||
translator.translateExceptionIfPossible(
|
||||
@@ -64,17 +67,33 @@ public class MongoExceptionTranslatorUnitTests {
|
||||
DuplicateKeyException.class, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void translateSocketException() {
|
||||
@Test // GH-3568
|
||||
void translateSocketException() {
|
||||
|
||||
expectExceptionWithCauseMessage(
|
||||
translator.translateExceptionIfPossible(new MongoSocketException("IOException", new ServerAddress())),
|
||||
DataAccessResourceFailureException.class, "IOException");
|
||||
translator.translateExceptionIfPossible(new MongoSocketException(EXCEPTION_MESSAGE, new ServerAddress())),
|
||||
DataAccessResourceFailureException.class, EXCEPTION_MESSAGE);
|
||||
}
|
||||
|
||||
@Test // GH-3568
|
||||
void translateSocketExceptionSubclasses() {
|
||||
|
||||
expectExceptionWithCauseMessage(
|
||||
translator.translateExceptionIfPossible(
|
||||
new MongoSocketWriteException("intermediate message", new ServerAddress(), new Exception(EXCEPTION_MESSAGE))
|
||||
),
|
||||
DataAccessResourceFailureException.class, EXCEPTION_MESSAGE);
|
||||
|
||||
expectExceptionWithCauseMessage(
|
||||
translator.translateExceptionIfPossible(
|
||||
new MongoSocketReadTimeoutException("intermediate message", new ServerAddress(), new Exception(EXCEPTION_MESSAGE))
|
||||
),
|
||||
DataAccessResourceFailureException.class, EXCEPTION_MESSAGE);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void translateCursorNotFound() throws UnknownHostException {
|
||||
void translateCursorNotFound() {
|
||||
|
||||
expectExceptionWithCauseMessage(
|
||||
translator.translateExceptionIfPossible(new MongoCursorNotFoundException(1L, new ServerAddress())),
|
||||
@@ -82,21 +101,21 @@ public class MongoExceptionTranslatorUnitTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void translateToDuplicateKeyException() {
|
||||
void translateToDuplicateKeyException() {
|
||||
|
||||
checkTranslatedMongoException(DuplicateKeyException.class, 11000);
|
||||
checkTranslatedMongoException(DuplicateKeyException.class, 11001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void translateToDataAccessResourceFailureException() {
|
||||
void translateToDataAccessResourceFailureException() {
|
||||
|
||||
checkTranslatedMongoException(DataAccessResourceFailureException.class, 12000);
|
||||
checkTranslatedMongoException(DataAccessResourceFailureException.class, 13440);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void translateToInvalidDataAccessApiUsageException() {
|
||||
void translateToInvalidDataAccessApiUsageException() {
|
||||
|
||||
checkTranslatedMongoException(InvalidDataAccessApiUsageException.class, 10003);
|
||||
checkTranslatedMongoException(InvalidDataAccessApiUsageException.class, 12001);
|
||||
@@ -106,7 +125,7 @@ public class MongoExceptionTranslatorUnitTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void translateToUncategorizedMongoDbException() {
|
||||
void translateToUncategorizedMongoDbException() {
|
||||
|
||||
MongoException exception = new MongoException(0, "");
|
||||
DataAccessException translatedException = translator.translateExceptionIfPossible(exception);
|
||||
@@ -115,7 +134,7 @@ public class MongoExceptionTranslatorUnitTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void translateMongoInternalException() {
|
||||
void translateMongoInternalException() {
|
||||
|
||||
MongoInternalException exception = new MongoInternalException("Internal exception");
|
||||
DataAccessException translatedException = translator.translateExceptionIfPossible(exception);
|
||||
@@ -124,14 +143,14 @@ public class MongoExceptionTranslatorUnitTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void translateUnsupportedException() {
|
||||
void translateUnsupportedException() {
|
||||
|
||||
RuntimeException exception = new RuntimeException();
|
||||
assertThat(translator.translateExceptionIfPossible(exception)).isNull();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2045
|
||||
public void translateSessionExceptions() {
|
||||
void translateSessionExceptions() {
|
||||
|
||||
checkTranslatedMongoException(ClientSessionException.class, 206);
|
||||
checkTranslatedMongoException(ClientSessionException.class, 213);
|
||||
@@ -140,7 +159,7 @@ public class MongoExceptionTranslatorUnitTests {
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2045
|
||||
public void translateTransactionExceptions() {
|
||||
void translateTransactionExceptions() {
|
||||
|
||||
checkTranslatedMongoException(MongoTransactionException.class, 217);
|
||||
checkTranslatedMongoException(MongoTransactionException.class, 225);
|
||||
@@ -163,13 +182,13 @@ public class MongoExceptionTranslatorUnitTests {
|
||||
assertThat(((MongoException) cause).getCode()).isEqualTo(code);
|
||||
}
|
||||
|
||||
private static void expectExceptionWithCauseMessage(NestedRuntimeException e,
|
||||
private static void expectExceptionWithCauseMessage(@Nullable NestedRuntimeException e,
|
||||
Class<? extends NestedRuntimeException> type) {
|
||||
expectExceptionWithCauseMessage(e, type, null);
|
||||
}
|
||||
|
||||
private static void expectExceptionWithCauseMessage(NestedRuntimeException e,
|
||||
Class<? extends NestedRuntimeException> type, String message) {
|
||||
private static void expectExceptionWithCauseMessage(@Nullable NestedRuntimeException e,
|
||||
Class<? extends NestedRuntimeException> type, @Nullable String message) {
|
||||
|
||||
assertThat(e).isInstanceOf(type);
|
||||
|
||||
|
||||
@@ -84,6 +84,7 @@ import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
|
||||
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.mapping.Sharded;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AfterConvertCallback;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AfterSaveCallback;
|
||||
@@ -1922,6 +1923,24 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
verify(findIterable, never()).first();
|
||||
}
|
||||
|
||||
@Test // GH-3590
|
||||
void shouldIncludeValueFromNestedShardKeyPath() {
|
||||
|
||||
WithShardKeyPointingToNested source = new WithShardKeyPointingToNested();
|
||||
source.id = "id-1";
|
||||
source.value = "v1";
|
||||
source.nested = new WithNamedFields();
|
||||
source.nested.customName = "cname";
|
||||
source.nested.name = "name";
|
||||
|
||||
template.save(source);
|
||||
|
||||
ArgumentCaptor<Bson> filter = ArgumentCaptor.forClass(Bson.class);
|
||||
verify(collection).replaceOne(filter.capture(), any(), any());
|
||||
|
||||
assertThat(filter.getValue()).isEqualTo(new Document("_id", "id-1").append("value", "v1").append("nested.custom-named-field", "cname"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2341
|
||||
void saveShouldProjectOnShardKeyWhenLoadingExistingDocument() {
|
||||
|
||||
@@ -2267,6 +2286,13 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
@Field("firstname") String name;
|
||||
}
|
||||
|
||||
@Sharded(shardKey = {"value", "nested.customName"})
|
||||
static class WithShardKeyPointingToNested {
|
||||
String id;
|
||||
String value;
|
||||
WithNamedFields nested;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mocks out the {@link MongoTemplate#getDb()} method to return the {@link DB} mock instead of executing the actual
|
||||
* behaviour.
|
||||
|
||||
@@ -781,7 +781,8 @@ public class QueryMapperUnitTests {
|
||||
Query query = query(byExample(probe).and("listOfItems").exists(true));
|
||||
org.bson.Document document = mapper.getMappedObject(query.getQueryObject(), context.getPersistentEntity(Foo.class));
|
||||
|
||||
assertThat(document).containsEntry("embedded\\._id", "conflux").containsEntry("my_items", new org.bson.Document("$exists", true));
|
||||
assertThat(document).containsEntry("embedded\\._id", "conflux").containsEntry("my_items",
|
||||
new org.bson.Document("$exists", true));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1988
|
||||
@@ -1011,6 +1012,76 @@ public class QueryMapperUnitTests {
|
||||
assertThat(target).isEqualTo(org.bson.Document.parse("{\"$text\" : { \"$search\" : \"test\" }}"));
|
||||
}
|
||||
|
||||
@Test // GH-3601
|
||||
void resolvesFieldnameWithUnderscoresCorrectly() {
|
||||
|
||||
Query query = query(where("fieldname_with_underscores").exists(true));
|
||||
|
||||
org.bson.Document document = mapper.getMappedObject(query.getQueryObject(),
|
||||
context.getPersistentEntity(WithPropertyUsingUnderscoreInName.class));
|
||||
|
||||
assertThat(document)
|
||||
.isEqualTo(new org.bson.Document("fieldname_with_underscores", new org.bson.Document("$exists", true)));
|
||||
}
|
||||
|
||||
@Test // GH-3601
|
||||
void resolvesMappedFieldnameWithUnderscoresCorrectly() {
|
||||
|
||||
Query query = query(where("renamed_fieldname_with_underscores").exists(true));
|
||||
|
||||
org.bson.Document document = mapper.getMappedObject(query.getQueryObject(),
|
||||
context.getPersistentEntity(WithPropertyUsingUnderscoreInName.class));
|
||||
|
||||
assertThat(document).isEqualTo(new org.bson.Document("renamed", new org.bson.Document("$exists", true)));
|
||||
}
|
||||
|
||||
@Test // GH-3601
|
||||
void resolvesSimpleNestedFieldnameWithUnderscoresCorrectly() {
|
||||
|
||||
Query query = query(where("simple.fieldname_with_underscores").exists(true));
|
||||
|
||||
org.bson.Document document = mapper.getMappedObject(query.getQueryObject(),
|
||||
context.getPersistentEntity(WrapperAroundWithPropertyUsingUnderscoreInName.class));
|
||||
|
||||
assertThat(document)
|
||||
.isEqualTo(new org.bson.Document("simple.fieldname_with_underscores", new org.bson.Document("$exists", true)));
|
||||
}
|
||||
|
||||
@Test // GH-3601
|
||||
void resolvesSimpleNestedMappedFieldnameWithUnderscoresCorrectly() {
|
||||
|
||||
Query query = query(where("simple.renamed_fieldname_with_underscores").exists(true));
|
||||
|
||||
org.bson.Document document = mapper.getMappedObject(query.getQueryObject(),
|
||||
context.getPersistentEntity(WrapperAroundWithPropertyUsingUnderscoreInName.class));
|
||||
|
||||
assertThat(document).isEqualTo(new org.bson.Document("simple.renamed", new org.bson.Document("$exists", true)));
|
||||
}
|
||||
|
||||
@Test // GH-3601
|
||||
void resolvesFieldNameWithUnderscoreOnNestedFieldnameWithUnderscoresCorrectly() {
|
||||
|
||||
Query query = query(where("double_underscore.fieldname_with_underscores").exists(true));
|
||||
|
||||
org.bson.Document document = mapper.getMappedObject(query.getQueryObject(),
|
||||
context.getPersistentEntity(WrapperAroundWithPropertyUsingUnderscoreInName.class));
|
||||
|
||||
assertThat(document).isEqualTo(
|
||||
new org.bson.Document("double_underscore.fieldname_with_underscores", new org.bson.Document("$exists", true)));
|
||||
}
|
||||
|
||||
@Test // GH-3601
|
||||
void resolvesFieldNameWithUnderscoreOnNestedMappedFieldnameWithUnderscoresCorrectly() {
|
||||
|
||||
Query query = query(where("double_underscore.renamed_fieldname_with_underscores").exists(true));
|
||||
|
||||
org.bson.Document document = mapper.getMappedObject(query.getQueryObject(),
|
||||
context.getPersistentEntity(WrapperAroundWithPropertyUsingUnderscoreInName.class));
|
||||
|
||||
assertThat(document)
|
||||
.isEqualTo(new org.bson.Document("double_underscore.renamed", new org.bson.Document("$exists", true)));
|
||||
}
|
||||
|
||||
class WithDeepArrayNesting {
|
||||
|
||||
List<WithNestedArray> level0;
|
||||
@@ -1194,4 +1265,17 @@ public class QueryMapperUnitTests {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
static class WrapperAroundWithPropertyUsingUnderscoreInName {
|
||||
|
||||
WithPropertyUsingUnderscoreInName simple;
|
||||
WithPropertyUsingUnderscoreInName double_underscore;
|
||||
}
|
||||
|
||||
static class WithPropertyUsingUnderscoreInName {
|
||||
|
||||
String fieldname_with_underscores;
|
||||
|
||||
@Field("renamed") String renamed_fieldname_with_underscores;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1089,6 +1089,38 @@ class UpdateMapperUnitTests {
|
||||
assertThat(mappedUpdate).isEqualTo(new Document("$set", new Document("aliased.$[element].value", 10)));
|
||||
}
|
||||
|
||||
@Test // GH-3552
|
||||
void numericKeyForMap() {
|
||||
|
||||
Update update = new Update().set("map.601218778970110001827396", "testing");
|
||||
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
|
||||
context.getPersistentEntity(EntityWithObjectMap.class));
|
||||
|
||||
assertThat(mappedUpdate).isEqualTo("{\"$set\": {\"map.601218778970110001827396\": \"testing\"}}");
|
||||
}
|
||||
|
||||
@Test // GH-3552
|
||||
void numericKeyInMapOfNestedPath() {
|
||||
|
||||
Update update = new Update().set("map.601218778970110001827396.value", "testing");
|
||||
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
|
||||
context.getPersistentEntity(EntityWithObjectMap.class));
|
||||
|
||||
assertThat(mappedUpdate)
|
||||
.isEqualTo("{\"$set\": {\"map.601218778970110001827396.value\": \"testing\"}}");
|
||||
}
|
||||
|
||||
@Test // GH-3566
|
||||
void mapsObjectClassPropertyFieldInMapValueTypeAsKey() {
|
||||
|
||||
Update update = new Update().set("map.class", "value");
|
||||
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
|
||||
context.getPersistentEntity(EntityWithObjectMap.class));
|
||||
|
||||
assertThat(mappedUpdate)
|
||||
.isEqualTo("{\"$set\": {\"map.class\": \"value\"}}");
|
||||
}
|
||||
|
||||
static class DomainTypeWrappingConcreteyTypeHavingListOfInterfaceTypeAttributes {
|
||||
ListModelWrapper concreteTypeWithListAttributeOfInterfaceType;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ import org.springframework.data.mongodb.core.schema.MongoJsonSchema;
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
* @author Andreas Zink
|
||||
* @author Clément Petit
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class CriteriaUnitTests {
|
||||
|
||||
@@ -310,9 +312,72 @@ public class CriteriaUnitTests {
|
||||
@Test // DATAMONGO-2002
|
||||
public void shouldEqualForSamePattern() {
|
||||
|
||||
Criteria left = new Criteria("field").regex("foo");
|
||||
Criteria right = new Criteria("field").regex("foo");
|
||||
|
||||
assertThat(left).isEqualTo(right);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2002
|
||||
public void shouldEqualForDocument() {
|
||||
|
||||
assertThat(new Criteria("field").is(new Document("one", 1).append("two", "two").append("null", null)))
|
||||
.isEqualTo(new Criteria("field").is(new Document("one", 1).append("two", "two").append("null", null)));
|
||||
|
||||
assertThat(new Criteria("field").is(new Document("one", 1).append("two", "two").append("null", null)))
|
||||
.isNotEqualTo(new Criteria("field").is(new Document("one", 1).append("two", "two")));
|
||||
|
||||
assertThat(new Criteria("field").is(new Document("one", 1).append("two", "two")))
|
||||
.isNotEqualTo(new Criteria("field").is(new Document("one", 1).append("two", "two").append("null", null)));
|
||||
|
||||
assertThat(new Criteria("field").is(new Document("one", 1).append("null", null).append("two", "two")))
|
||||
.isNotEqualTo(new Criteria("field").is(new Document("one", 1).append("two", "two").append("null", null)));
|
||||
|
||||
assertThat(new Criteria("field").is(new Document())).isNotEqualTo(new Criteria("field").is("foo"));
|
||||
assertThat(new Criteria("field").is("foo")).isNotEqualTo(new Criteria("field").is(new Document()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2002
|
||||
public void shouldEqualForCollection() {
|
||||
|
||||
assertThat(new Criteria("field").is(Arrays.asList("foo", "bar")))
|
||||
.isEqualTo(new Criteria("field").is(Arrays.asList("foo", "bar")));
|
||||
|
||||
assertThat(new Criteria("field").is(Arrays.asList("foo", 1)))
|
||||
.isNotEqualTo(new Criteria("field").is(Arrays.asList("foo", "bar")));
|
||||
|
||||
assertThat(new Criteria("field").is(Collections.singletonList("foo")))
|
||||
.isNotEqualTo(new Criteria("field").is(Arrays.asList("foo", "bar")));
|
||||
|
||||
assertThat(new Criteria("field").is(Arrays.asList("foo", "bar")))
|
||||
.isNotEqualTo(new Criteria("field").is(Collections.singletonList("foo")));
|
||||
|
||||
assertThat(new Criteria("field").is(Arrays.asList("foo", "bar"))).isNotEqualTo(new Criteria("field").is("foo"));
|
||||
|
||||
assertThat(new Criteria("field").is("foo")).isNotEqualTo(new Criteria("field").is(Arrays.asList("foo", "bar")));
|
||||
}
|
||||
|
||||
@Test // GH-3414
|
||||
public void shouldEqualForSamePatternAndFlags() {
|
||||
|
||||
Criteria left = new Criteria("field").regex("foo", "iu");
|
||||
Criteria right = new Criteria("field").regex("foo");
|
||||
|
||||
assertThat(left).isNotEqualTo(right);
|
||||
}
|
||||
|
||||
@Test // GH-3414
|
||||
public void shouldEqualForNestedPattern() {
|
||||
|
||||
Criteria left = new Criteria("a").orOperator(
|
||||
new Criteria("foo").regex("value", "i"),
|
||||
new Criteria("bar").regex("value")
|
||||
);
|
||||
Criteria right = new Criteria("a").orOperator(
|
||||
new Criteria("foo").regex("value", "i"),
|
||||
new Criteria("bar").regex("value")
|
||||
);
|
||||
|
||||
assertThat(left).isEqualTo(right);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1363,4 +1363,19 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
|
||||
assertThat(repository.findWithSpelByFirstnameForSpELExpressionWithParameterIndexOnly("Dave")).containsExactly(dave);
|
||||
assertThat(repository.findWithSpelByFirstnameForSpELExpressionWithParameterIndexOnly("Carter")).containsExactly(carter);
|
||||
}
|
||||
|
||||
@Test // GH-3395
|
||||
void caseInSensitiveInClause() {
|
||||
assertThat(repository.findByLastnameIgnoreCaseIn("bEAuFoRd", "maTTheWs")).hasSize(3);
|
||||
}
|
||||
|
||||
@Test // GH-3395
|
||||
void caseInSensitiveInClauseQuotesExpressions() {
|
||||
assertThat(repository.findByLastnameIgnoreCaseIn(".*")).isEmpty();
|
||||
}
|
||||
|
||||
@Test // GH-3395
|
||||
void caseSensitiveInClauseIgnoresExpressions() {
|
||||
assertThat(repository.findByFirstnameIn(".*")).isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +125,8 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
|
||||
@Query("{ 'lastname' : { '$regex' : '?0', '$options' : 'i'}}")
|
||||
Page<Person> findByLastnameLikeWithPageable(String lastname, Pageable pageable);
|
||||
|
||||
List<Person> findByLastnameIgnoreCaseIn(String... lastname);
|
||||
|
||||
/**
|
||||
* Returns all {@link Person}s with a firstname contained in the given varargs.
|
||||
*
|
||||
|
||||
@@ -20,15 +20,19 @@ import static org.springframework.data.domain.ExampleMatcher.*;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Value;
|
||||
import lombok.With;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
@@ -44,10 +48,9 @@ import org.springframework.data.domain.Sort.Order;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
|
||||
import org.springframework.data.mongodb.repository.support.ReactiveMongoRepositoryFactory;
|
||||
import org.springframework.data.mongodb.repository.support.SimpleReactiveMongoRepository;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
@@ -56,19 +59,22 @@ import org.springframework.util.ClassUtils;
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @author Ruben J Garcia
|
||||
* @author Clément Petit
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration("classpath:reactive-infrastructure.xml")
|
||||
public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware, BeanFactoryAware {
|
||||
|
||||
@Autowired private ReactiveMongoTemplate template;
|
||||
|
||||
ReactiveMongoRepositoryFactory factory;
|
||||
ClassLoader classLoader;
|
||||
BeanFactory beanFactory;
|
||||
ReactivePersonRepostitory repository;
|
||||
private ReactiveMongoRepositoryFactory factory;
|
||||
private ClassLoader classLoader;
|
||||
private BeanFactory beanFactory;
|
||||
private ReactivePersonRepository repository;
|
||||
private ReactiveImmutablePersonRepository immutableRepository;
|
||||
|
||||
private ReactivePerson dave, oliver, carter, boyd, stefan, leroi, alicia;
|
||||
private ImmutableReactivePerson keith, james, mariah;
|
||||
|
||||
@Override
|
||||
public void setBeanClassLoader(ClassLoader classLoader) {
|
||||
@@ -80,8 +86,8 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware,
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
|
||||
factory = new ReactiveMongoRepositoryFactory(template);
|
||||
factory.setRepositoryBaseClass(SimpleReactiveMongoRepository.class);
|
||||
@@ -89,9 +95,11 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware,
|
||||
factory.setBeanFactory(beanFactory);
|
||||
factory.setEvaluationContextProvider(ReactiveQueryMethodEvaluationContextProvider.DEFAULT);
|
||||
|
||||
repository = factory.getRepository(ReactivePersonRepostitory.class);
|
||||
repository = factory.getRepository(ReactivePersonRepository.class);
|
||||
immutableRepository = factory.getRepository(ReactiveImmutablePersonRepository.class);
|
||||
|
||||
repository.deleteAll().as(StepVerifier::create).verifyComplete();
|
||||
immutableRepository.deleteAll().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
dave = new ReactivePerson("Dave", "Matthews", 42);
|
||||
oliver = new ReactivePerson("Oliver August", "Matthews", 4);
|
||||
@@ -100,6 +108,9 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware,
|
||||
stefan = new ReactivePerson("Stefan", "Lessard", 34);
|
||||
leroi = new ReactivePerson("Leroi", "Moore", 41);
|
||||
alicia = new ReactivePerson("Alicia", "Keys", 30);
|
||||
keith = new ImmutableReactivePerson(null, "Keith", "Urban", 53);
|
||||
james = new ImmutableReactivePerson(null, "James", "Arthur", 33);
|
||||
mariah = new ImmutableReactivePerson(null, "Mariah", "Carey", 51);
|
||||
|
||||
repository.saveAll(Arrays.asList(oliver, dave, carter, boyd, stefan, leroi, alicia)).as(StepVerifier::create) //
|
||||
.expectNextCount(7) //
|
||||
@@ -107,78 +118,78 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware,
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1444
|
||||
public void existsByIdShouldReturnTrueForExistingObject() {
|
||||
void existsByIdShouldReturnTrueForExistingObject() {
|
||||
repository.existsById(dave.id).as(StepVerifier::create).expectNext(true).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1444
|
||||
public void existsByIdShouldReturnFalseForAbsentObject() {
|
||||
void existsByIdShouldReturnFalseForAbsentObject() {
|
||||
repository.existsById("unknown").as(StepVerifier::create).expectNext(false).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1444
|
||||
public void existsByMonoOfIdShouldReturnTrueForExistingObject() {
|
||||
void existsByMonoOfIdShouldReturnTrueForExistingObject() {
|
||||
repository.existsById(Mono.just(dave.id)).as(StepVerifier::create).expectNext(true).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1712
|
||||
public void existsByFluxOfIdShouldReturnTrueForExistingObject() {
|
||||
void existsByFluxOfIdShouldReturnTrueForExistingObject() {
|
||||
repository.existsById(Flux.just(dave.id, oliver.id)).as(StepVerifier::create).expectNext(true).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1444
|
||||
public void existsByEmptyMonoOfIdShouldReturnEmptyMono() {
|
||||
void existsByEmptyMonoOfIdShouldReturnEmptyMono() {
|
||||
repository.existsById(Mono.empty()).as(StepVerifier::create).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1444
|
||||
public void findByIdShouldReturnObject() {
|
||||
void findByIdShouldReturnObject() {
|
||||
repository.findById(dave.id).as(StepVerifier::create).expectNext(dave).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1444
|
||||
public void findByIdShouldCompleteWithoutValueForAbsentObject() {
|
||||
void findByIdShouldCompleteWithoutValueForAbsentObject() {
|
||||
repository.findById("unknown").as(StepVerifier::create).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1444
|
||||
public void findByIdByMonoOfIdShouldReturnTrueForExistingObject() {
|
||||
void findByIdByMonoOfIdShouldReturnTrueForExistingObject() {
|
||||
repository.findById(Mono.just(dave.id)).as(StepVerifier::create).expectNext(dave).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1712
|
||||
public void findByIdByFluxOfIdShouldReturnTrueForExistingObject() {
|
||||
void findByIdByFluxOfIdShouldReturnTrueForExistingObject() {
|
||||
repository.findById(Flux.just(dave.id, oliver.id)).as(StepVerifier::create).expectNext(dave).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1444
|
||||
public void findByIdByEmptyMonoOfIdShouldReturnEmptyMono() {
|
||||
void findByIdByEmptyMonoOfIdShouldReturnEmptyMono() {
|
||||
repository.findById(Mono.empty()).as(StepVerifier::create).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1444
|
||||
public void findAllShouldReturnAllResults() {
|
||||
void findAllShouldReturnAllResults() {
|
||||
repository.findAll().as(StepVerifier::create).expectNextCount(7).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1444
|
||||
public void findAllByIterableOfIdShouldReturnResults() {
|
||||
void findAllByIterableOfIdShouldReturnResults() {
|
||||
repository.findAllById(Arrays.asList(dave.id, boyd.id)).as(StepVerifier::create).expectNextCount(2)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1444
|
||||
public void findAllByPublisherOfIdShouldReturnResults() {
|
||||
void findAllByPublisherOfIdShouldReturnResults() {
|
||||
repository.findAllById(Flux.just(dave.id, boyd.id)).as(StepVerifier::create).expectNextCount(2).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1444
|
||||
public void findAllByEmptyPublisherOfIdShouldReturnResults() {
|
||||
void findAllByEmptyPublisherOfIdShouldReturnResults() {
|
||||
repository.findAllById(Flux.empty()).as(StepVerifier::create).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1444
|
||||
public void findAllWithSortShouldReturnResults() {
|
||||
void findAllWithSortShouldReturnResults() {
|
||||
|
||||
repository.findAll(Sort.by(new Order(Direction.ASC, "age"))).as(StepVerifier::create) //
|
||||
.expectNextCount(7) //
|
||||
@@ -186,12 +197,12 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware,
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1444
|
||||
public void countShouldReturnNumberOfRecords() {
|
||||
void countShouldReturnNumberOfRecords() {
|
||||
repository.count().as(StepVerifier::create).expectNext(7L).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1444
|
||||
public void insertEntityShouldInsertEntity() {
|
||||
void insertEntityShouldInsertEntity() {
|
||||
|
||||
repository.deleteAll().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
@@ -203,7 +214,7 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware,
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1444
|
||||
public void insertShouldDeferredWrite() {
|
||||
void insertShouldDeferredWrite() {
|
||||
|
||||
ReactivePerson person = new ReactivePerson("Homer", "Simpson", 36);
|
||||
|
||||
@@ -213,7 +224,7 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware,
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1444
|
||||
public void insertIterableOfEntitiesShouldInsertEntity() {
|
||||
void insertIterableOfEntitiesShouldInsertEntity() {
|
||||
|
||||
repository.deleteAll().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
@@ -231,7 +242,7 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware,
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1444
|
||||
public void insertPublisherOfEntitiesShouldInsertEntity() {
|
||||
void insertPublisherOfEntitiesShouldInsertEntity() {
|
||||
|
||||
repository.deleteAll().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
@@ -247,7 +258,7 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware,
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1444
|
||||
public void saveEntityShouldUpdateExistingEntity() {
|
||||
void saveEntityShouldUpdateExistingEntity() {
|
||||
|
||||
dave.setFirstname("Hello, Dave");
|
||||
dave.setLastname("Bowman");
|
||||
@@ -264,7 +275,7 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware,
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1444
|
||||
public void saveEntityShouldInsertNewEntity() {
|
||||
void saveEntityShouldInsertNewEntity() {
|
||||
|
||||
ReactivePerson person = new ReactivePerson("Homer", "Simpson", 36);
|
||||
|
||||
@@ -278,7 +289,7 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware,
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1444
|
||||
public void saveIterableOfNewEntitiesShouldInsertEntity() {
|
||||
void saveIterableOfNewEntitiesShouldInsertEntity() {
|
||||
|
||||
repository.deleteAll().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
@@ -294,7 +305,7 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware,
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1444
|
||||
public void saveIterableOfMixedEntitiesShouldInsertEntity() {
|
||||
void saveIterableOfMixedEntitiesShouldInsertEntity() {
|
||||
|
||||
ReactivePerson person = new ReactivePerson("Homer", "Simpson", 36);
|
||||
|
||||
@@ -310,7 +321,7 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware,
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1444
|
||||
public void savePublisherOfEntitiesShouldInsertEntity() {
|
||||
void savePublisherOfEntitiesShouldInsertEntity() {
|
||||
|
||||
repository.deleteAll().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
@@ -325,8 +336,20 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware,
|
||||
assertThat(boyd.getId()).isNotNull();
|
||||
}
|
||||
|
||||
@Test // GH-3609
|
||||
void savePublisherOfImmutableEntitiesShouldInsertEntity() {
|
||||
|
||||
immutableRepository.deleteAll().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
immutableRepository.saveAll(Flux.just(keith)).as(StepVerifier::create) //
|
||||
.consumeNextWith(actual -> {
|
||||
assertThat(actual.id).isNotNull();
|
||||
}) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1444
|
||||
public void deleteAllShouldRemoveEntities() {
|
||||
void deleteAllShouldRemoveEntities() {
|
||||
|
||||
repository.deleteAll().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
@@ -334,7 +357,7 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware,
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1444
|
||||
public void deleteByIdShouldRemoveEntity() {
|
||||
void deleteByIdShouldRemoveEntity() {
|
||||
|
||||
repository.deleteById(dave.id).as(StepVerifier::create).verifyComplete();
|
||||
|
||||
@@ -342,7 +365,7 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware,
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1712
|
||||
public void deleteByIdUsingMonoShouldRemoveEntity() {
|
||||
void deleteByIdUsingMonoShouldRemoveEntity() {
|
||||
|
||||
repository.deleteById(Mono.just(dave.id)).as(StepVerifier::create).verifyComplete();
|
||||
|
||||
@@ -350,7 +373,7 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware,
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1712
|
||||
public void deleteByIdUsingFluxShouldRemoveEntity() {
|
||||
void deleteByIdUsingFluxShouldRemoveEntity() {
|
||||
|
||||
repository.deleteById(Flux.just(dave.id, oliver.id)).as(StepVerifier::create).verifyComplete();
|
||||
|
||||
@@ -359,7 +382,7 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware,
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1444
|
||||
public void deleteShouldRemoveEntity() {
|
||||
void deleteShouldRemoveEntity() {
|
||||
|
||||
repository.delete(dave).as(StepVerifier::create).verifyComplete();
|
||||
|
||||
@@ -368,7 +391,7 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware,
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1444
|
||||
public void deleteIterableOfEntitiesShouldRemoveEntities() {
|
||||
void deleteIterableOfEntitiesShouldRemoveEntities() {
|
||||
|
||||
repository.deleteAll(Arrays.asList(dave, boyd)).as(StepVerifier::create).verifyComplete();
|
||||
|
||||
@@ -378,7 +401,7 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware,
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1444
|
||||
public void deletePublisherOfEntitiesShouldRemoveEntities() {
|
||||
void deletePublisherOfEntitiesShouldRemoveEntities() {
|
||||
|
||||
repository.deleteAll(Flux.just(dave, boyd)).as(StepVerifier::create).verifyComplete();
|
||||
|
||||
@@ -388,7 +411,7 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware,
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1619
|
||||
public void findOneByExampleShouldReturnObject() {
|
||||
void findOneByExampleShouldReturnObject() {
|
||||
|
||||
Example<ReactivePerson> example = Example.of(dave);
|
||||
|
||||
@@ -396,7 +419,7 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware,
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1619
|
||||
public void findAllByExampleShouldReturnObjects() {
|
||||
void findAllByExampleShouldReturnObjects() {
|
||||
|
||||
Example<ReactivePerson> example = Example.of(dave, matching().withIgnorePaths("id", "age", "firstname"));
|
||||
|
||||
@@ -404,7 +427,7 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware,
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1619
|
||||
public void findAllByExampleAndSortShouldReturnObjects() {
|
||||
void findAllByExampleAndSortShouldReturnObjects() {
|
||||
|
||||
Example<ReactivePerson> example = Example.of(dave, matching().withIgnorePaths("id", "age", "firstname"));
|
||||
|
||||
@@ -413,7 +436,7 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware,
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1619
|
||||
public void countByExampleShouldCountObjects() {
|
||||
void countByExampleShouldCountObjects() {
|
||||
|
||||
Example<ReactivePerson> example = Example.of(dave, matching().withIgnorePaths("id", "age", "firstname"));
|
||||
|
||||
@@ -421,7 +444,7 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware,
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1619
|
||||
public void existsByExampleShouldReturnExisting() {
|
||||
void existsByExampleShouldReturnExisting() {
|
||||
|
||||
Example<ReactivePerson> example = Example.of(dave, matching().withIgnorePaths("id", "age", "firstname"));
|
||||
|
||||
@@ -429,7 +452,7 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware,
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1619
|
||||
public void existsByExampleShouldReturnNonExisting() {
|
||||
void existsByExampleShouldReturnNonExisting() {
|
||||
|
||||
Example<ReactivePerson> example = Example.of(new ReactivePerson("foo", "bar", -1));
|
||||
|
||||
@@ -437,7 +460,7 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware,
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1619
|
||||
public void findOneShouldEmitIncorrectResultSizeDataAccessExceptionWhenMoreThanOneElementFound() {
|
||||
void findOneShouldEmitIncorrectResultSizeDataAccessExceptionWhenMoreThanOneElementFound() {
|
||||
|
||||
Example<ReactivePerson> example = Example.of(new ReactivePerson(null, "Matthews", -1),
|
||||
matching().withIgnorePaths("age"));
|
||||
@@ -446,19 +469,23 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware,
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1907
|
||||
public void findOneByExampleWithoutResultShouldCompleteEmpty() {
|
||||
void findOneByExampleWithoutResultShouldCompleteEmpty() {
|
||||
|
||||
Example<ReactivePerson> example = Example.of(new ReactivePerson("foo", "bar", -1));
|
||||
|
||||
repository.findOne(example).as(StepVerifier::create).verifyComplete();
|
||||
}
|
||||
|
||||
interface ReactivePersonRepostitory extends ReactiveMongoRepository<ReactivePerson, String> {
|
||||
interface ReactivePersonRepository extends ReactiveMongoRepository<ReactivePerson, String> {
|
||||
|
||||
Flux<ReactivePerson> findByLastname(String lastname);
|
||||
|
||||
}
|
||||
|
||||
interface ReactiveImmutablePersonRepository extends ReactiveMongoRepository<ImmutableReactivePerson, String> {
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
static class ReactivePerson {
|
||||
@@ -469,11 +496,30 @@ public class SimpleReactiveMongoRepositoryTests implements BeanClassLoaderAware,
|
||||
String lastname;
|
||||
int age;
|
||||
|
||||
public ReactivePerson(String firstname, String lastname, int age) {
|
||||
ReactivePerson(String firstname, String lastname, int age) {
|
||||
|
||||
this.firstname = firstname;
|
||||
this.lastname = lastname;
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
|
||||
@With
|
||||
@Value
|
||||
static class ImmutableReactivePerson {
|
||||
|
||||
@Id String id;
|
||||
|
||||
String firstname;
|
||||
String lastname;
|
||||
int age;
|
||||
|
||||
ImmutableReactivePerson(@Nullable String id, String firstname, String lastname, int age) {
|
||||
this.id = id;
|
||||
this.firstname = firstname;
|
||||
this.lastname = lastname;
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -159,6 +159,14 @@ public class StringBasedAggregationUnitTests {
|
||||
assertThat(executeAggregation("returnCollection").result).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test // GH-3623
|
||||
public void returnNullWhenSingleResultIsNotPresent() {
|
||||
|
||||
when(aggregationResults.getMappedResults()).thenReturn(Collections.emptyList());
|
||||
|
||||
assertThat(executeAggregation("simpleReturnType").result).isNull();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2153
|
||||
public void returnRawResultType() {
|
||||
assertThat(executeAggregation("returnRawResultType").result).isEqualTo(aggregationResults);
|
||||
@@ -312,6 +320,9 @@ public class StringBasedAggregationUnitTests {
|
||||
|
||||
@Aggregation(RAW_GROUP_BY_LASTNAME_STRING)
|
||||
Page<Person> invalidPageReturnType(Pageable page);
|
||||
|
||||
@Aggregation(RAW_GROUP_BY_LASTNAME_STRING)
|
||||
String simpleReturnType();
|
||||
}
|
||||
|
||||
static class PersonAggregate {
|
||||
|
||||
@@ -1886,8 +1886,6 @@ AggregationResults<TagCount> results = template.aggregate(aggregation, "tags", T
|
||||
|
||||
WARNING: Indexes are only used if the collation used for the operation matches the index collation.
|
||||
|
||||
include::./mongo-json-schema.adoc[leveloffset=+1]
|
||||
|
||||
<<mongo.repositories>> support `Collations` via the `collation` attribute of the `@Query` annotation.
|
||||
|
||||
.Collation support for Repositories
|
||||
@@ -1928,186 +1926,7 @@ as shown in (1) and (2), will be included when creating the index.
|
||||
TIP: The most specifc `Collation` outroules potentially defined others. Which means Method argument over query method annotation over doamin type annotation.
|
||||
====
|
||||
|
||||
[[mongo.jsonSchema]]
|
||||
=== JSON Schema
|
||||
|
||||
As of version 3.6, MongoDB supports collections that validate documents against a provided https://docs.mongodb.com/manual/core/schema-validation/#json-schema[JSON Schema].
|
||||
The schema itself and both validation action and level can be defined when creating the collection, as the following example shows:
|
||||
|
||||
.Sample JSON schema
|
||||
====
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
"type": "object", <1>
|
||||
|
||||
"required": [ "firstname", "lastname" ], <2>
|
||||
|
||||
"properties": { <3>
|
||||
|
||||
"firstname": { <4>
|
||||
"type": "string",
|
||||
"enum": [ "luke", "han" ]
|
||||
},
|
||||
"address": { <5>
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"postCode": { "type": "string", "minLength": 4, "maxLength": 5 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> JSON schema documents always describe a whole document from its root. A schema is a schema object itself that can contain
|
||||
embedded schema objects that describe properties and subdocuments.
|
||||
<2> `required` is a property that describes which properties are required in a document. It can be specified optionally, along with other
|
||||
schema constraints. See MongoDB's documentation on https://docs.mongodb.com/manual/reference/operator/query/jsonSchema/#available-keywords[available keywords].
|
||||
<3> `properties` is related to a schema object that describes an `object` type. It contains property-specific schema constraints.
|
||||
<4> `firstname` specifies constraints for the `firsname` field inside the document. Here, it is a string-based `properties` element declaring
|
||||
possible field values.
|
||||
<5> `address` is a subdocument defining a schema for values in its `postCode` field.
|
||||
====
|
||||
|
||||
You can provide a schema either by specifying a schema document (that is, by using the `Document` API to parse or build a document object) or by building it with Spring Data's JSON schema utilities in `org.springframework.data.mongodb.core.schema`. `MongoJsonSchema` is the entry point for all JSON schema-related operations. The following example shows how use `MongoJsonSchema.builder()` to create a JSON schema:
|
||||
|
||||
.Creating a JSON schema
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
MongoJsonSchema.builder() <1>
|
||||
.required("firstname", "lastname") <2>
|
||||
|
||||
.properties(
|
||||
string("firstname").possibleValues("luke", "han"), <3>
|
||||
|
||||
object("address")
|
||||
.properties(string("postCode").minLength(4).maxLength(5)))
|
||||
|
||||
.build(); <4>
|
||||
----
|
||||
<1> Obtain a schema builder to configure the schema with a fluent API.
|
||||
<2> Configure required properties.
|
||||
<3> Configure the String-typed `firstname` field, allowing only `luke` and `han` values. Properties can be typed or untyped. Use a static import of `JsonSchemaProperty` to make the syntax slightly more compact and to get entry points such as `string(…)`.
|
||||
<4> Build the schema object. Use the schema to create either a collection or <<mongodb-template-query.criteria,query documents>>.
|
||||
====
|
||||
|
||||
There are already some predefined and strongly typed schema objects (`JsonSchemaObject` and `JsonSchemaProperty`) available
|
||||
through static methods on the gateway interfaces.
|
||||
However, you may need to build custom property validation rules, which can be created through the builder API, as the following example shows:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
// "birthdate" : { "bsonType": "date" }
|
||||
JsonSchemaProperty.named("birthdate").ofType(Type.dateType());
|
||||
|
||||
// "birthdate" : { "bsonType": "date", "description", "Must be a date" }
|
||||
JsonSchemaProperty.named("birthdate").with(JsonSchemaObject.of(Type.dateType()).description("Must be a date"));
|
||||
----
|
||||
|
||||
The Schema builder also provides support for https://docs.mongodb.com/manual/core/security-client-side-encryption/[Client-Side Field Level Encryption]. Please refer to <<mongo.jsonSchema.encrypted-fields>> for more information,
|
||||
|
||||
`CollectionOptions` provides the entry point to schema support for collections, as the following example shows:
|
||||
|
||||
.Create collection with `$jsonSchema`
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
MongoJsonSchema schema = MongoJsonSchema.builder().required("firstname", "lastname").build();
|
||||
|
||||
template.createCollection(Person.class, CollectionOptions.empty().schema(schema));
|
||||
----
|
||||
====
|
||||
|
||||
You can use a schema to query any collection for documents that match a given structure defined by a JSON schema, as the following example shows:
|
||||
|
||||
.Query for Documents matching a `$jsonSchema`
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
MongoJsonSchema schema = MongoJsonSchema.builder().required("firstname", "lastname").build();
|
||||
|
||||
template.find(query(matchingDocumentStructure(schema)), Person.class);
|
||||
----
|
||||
====
|
||||
|
||||
The following table shows the supported JSON schema types:
|
||||
|
||||
[cols="3,1,6", options="header"]
|
||||
.Supported JSON schema types
|
||||
|===
|
||||
| Schema Type
|
||||
| Java Type
|
||||
| Schema Properties
|
||||
|
||||
| `untyped`
|
||||
| -
|
||||
| `description`, generated `description`, `enum`, `allOf`, `anyOf`, `oneOf`, `not`
|
||||
|
||||
| `object`
|
||||
| `Object`
|
||||
| `required`, `additionalProperties`, `properties`, `minProperties`, `maxProperties`, `patternProperties`
|
||||
|
||||
| `array`
|
||||
| any array except `byte[]`
|
||||
| `uniqueItems`, `additionalItems`, `items`, `minItems`, `maxItems`
|
||||
|
||||
| `string`
|
||||
| `String`
|
||||
| `minLength`, `maxLentgth`, `pattern`
|
||||
|
||||
| `int`
|
||||
| `int`, `Integer`
|
||||
| `multipleOf`, `minimum`, `exclusiveMinimum`, `maximum`, `exclusiveMaximum`
|
||||
|
||||
| `long`
|
||||
| `long`, `Long`
|
||||
| `multipleOf`, `minimum`, `exclusiveMinimum`, `maximum`, `exclusiveMaximum`
|
||||
|
||||
| `double`
|
||||
| `float`, `Float`, `double`, `Double`
|
||||
| `multipleOf`, `minimum`, `exclusiveMinimum`, `maximum`, `exclusiveMaximum`
|
||||
|
||||
| `decimal`
|
||||
| `BigDecimal`
|
||||
| `multipleOf`, `minimum`, `exclusiveMinimum`, `maximum`, `exclusiveMaximum`
|
||||
|
||||
| `number`
|
||||
| `Number`
|
||||
| `multipleOf`, `minimum`, `exclusiveMinimum`, `maximum`, `exclusiveMaximum`
|
||||
|
||||
| `binData`
|
||||
| `byte[]`
|
||||
| (none)
|
||||
|
||||
| `boolean`
|
||||
| `boolean`, `Boolean`
|
||||
| (none)
|
||||
|
||||
| `null`
|
||||
| `null`
|
||||
| (none)
|
||||
|
||||
| `objectId`
|
||||
| `ObjectId`
|
||||
| (none)
|
||||
|
||||
| `date`
|
||||
| `java.util.Date`
|
||||
| (none)
|
||||
|
||||
| `timestamp`
|
||||
| `BsonTimestamp`
|
||||
| (none)
|
||||
|
||||
| `regex`
|
||||
| `java.util.regex.Pattern`
|
||||
| (none)
|
||||
|
||||
|===
|
||||
|
||||
NOTE: `untyped` is a generic type that is inherited by all typed schema types. It provides all `untyped` schema properties to typed schema types.
|
||||
|
||||
For more information, see https://docs.mongodb.com/manual/reference/operator/query/jsonSchema/#op._S_jsonSchema[$jsonSchema].
|
||||
include::./mongo-json-schema.adoc[leveloffset=+1]
|
||||
|
||||
[[mongo.query.fluent-template-api]]
|
||||
=== Fluent Template API
|
||||
|
||||
@@ -1,6 +1,80 @@
|
||||
Spring Data MongoDB Changelog
|
||||
=============================
|
||||
|
||||
Changes in version 3.1.8 (2021-04-14)
|
||||
-------------------------------------
|
||||
* #3623 - `@Aggregation` repository query method causes `NullPointerException` when the result is empty.
|
||||
* #3601 - Criteria object not allowing to use field names with underscore in them.
|
||||
* #3414 - Criteria or toEquals fail if contains regex [DATAMONGO-2559].
|
||||
|
||||
|
||||
Changes in version 3.0.9.RELEASE (2021-04-14)
|
||||
---------------------------------------------
|
||||
* #3623 - `@Aggregation` repository query method causes `NullPointerException` when the result is empty.
|
||||
* #3609 - SimpleReactiveMongoRepository#saveAll does not populate @Id property if it is immutable.
|
||||
* #3414 - Criteria or toEquals fail if contains regex [DATAMONGO-2559].
|
||||
|
||||
|
||||
Changes in version 3.1.7 (2021-03-31)
|
||||
-------------------------------------
|
||||
* #3613 - Use StringUtils.replace(…) instead of String.replaceAll(…) for mapKeyDotReplacement.
|
||||
* #3609 - SimpleReactiveMongoRepository#saveAll does not populate @Id property if it is immutable.
|
||||
|
||||
|
||||
Changes in version 3.2.0-RC1 (2021-03-31)
|
||||
-----------------------------------------
|
||||
* #3613 - Use StringUtils.replace(…) instead of String.replaceAll(…) for mapKeyDotReplacement.
|
||||
* #3609 - SimpleReactiveMongoRepository#saveAll does not populate @Id property if it is immutable.
|
||||
* #3600 - Rename Embedded annotation -> Unwrapped.
|
||||
* #3583 - Support aggregation expression on fields projection.
|
||||
|
||||
|
||||
Changes in version 3.2.0-M5 (2021-03-17)
|
||||
----------------------------------------
|
||||
* #3592 - Remove @Persistent from entity-scan include filters.
|
||||
* #3590 - Embedded sharding keys are not correctly picked up from the shardKeySource Document.
|
||||
* #3580 - Fix CustomConverter conversion lookup.
|
||||
* #3579 - Upgrade to MongoDB Java Drivers 4.2.2.
|
||||
* #3575 - Introduce ConversionContext and clean up MappingMongoConverter.
|
||||
* #3573 - Json Schema section appears twice in reference documentation.
|
||||
* #3571 - Introduce ConversionContext and clean up MappingMongoConverter.
|
||||
* #3570 - Incorrect class casting cause ClassCastException when save java.util.Collection using MongoTemplate.
|
||||
* #3568 - MongoSocketWriteException may be translated into DataAccessResourceFailureException.
|
||||
* #3566 - Couldn't find PersistentEntity for type java.lang.Object when updating a field with suffix "class".
|
||||
* #3552 - UpdateMapper drops numeric keys in Maps.
|
||||
* #3395 - Derived findBy…IgnoreCaseIn query doesn't return expected results [DATAMONGO-2540].
|
||||
* #3286 - Add possibility to use Collection<Criteria> as parameter in and/or/nor operators [DATAMONGO-2428].
|
||||
* #2911 - ensureNotIterable in MongoTemplate only checks for array type [DATAMONGO-2044].
|
||||
* #590 - DATAMONGO-2044 make ensureNotIterable actually check if object is iterable.
|
||||
|
||||
|
||||
Changes in version 3.1.6 (2021-03-17)
|
||||
-------------------------------------
|
||||
* #3592 - Remove @Persistent from entity-scan include filters.
|
||||
* #3590 - Embedded sharding keys are not correctly picked up from the shardKeySource Document.
|
||||
* #3589 - Upgrade to MongoDB Driver 4.1.2.
|
||||
* #3573 - Json Schema section appears twice in reference documentation.
|
||||
* #3568 - MongoSocketWriteException may be translated into DataAccessResourceFailureException.
|
||||
* #3566 - Couldn't find PersistentEntity for type java.lang.Object when updating a field with suffix "class".
|
||||
* #3552 - UpdateMapper drops numeric keys in Maps.
|
||||
* #3395 - Derived findBy…IgnoreCaseIn query doesn't return expected results [DATAMONGO-2540].
|
||||
|
||||
|
||||
Changes in version 3.0.8.RELEASE (2021-03-17)
|
||||
---------------------------------------------
|
||||
* #3590 - Embedded sharding keys are not correctly picked up from the shardKeySource Document.
|
||||
* #3588 - Upgrade to MongoDB Driver 4.0.6.
|
||||
* #3573 - Json Schema section appears twice in reference documentation.
|
||||
* #3568 - MongoSocketWriteException may be translated into DataAccessResourceFailureException.
|
||||
* #3566 - Couldn't find PersistentEntity for type java.lang.Object when updating a field with suffix "class".
|
||||
* #3552 - UpdateMapper drops numeric keys in Maps.
|
||||
* #3395 - Derived findBy…IgnoreCaseIn query doesn't return expected results [DATAMONGO-2540].
|
||||
|
||||
|
||||
Changes in version 3.2.0-M4 (2021-02-18)
|
||||
----------------------------------------
|
||||
|
||||
|
||||
Changes in version 3.1.5 (2021-02-18)
|
||||
-------------------------------------
|
||||
|
||||
@@ -3330,6 +3404,14 @@ Repository
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Spring Data MongoDB 3.1.5 (2020.0.5)
|
||||
Spring Data MongoDB 3.1.8 (2020.0.8)
|
||||
Copyright (c) [2010-2019] Pivotal Software, Inc.
|
||||
|
||||
This product is licensed to you under the Apache License, Version 2.0 (the "License").
|
||||
@@ -25,3 +25,6 @@ conditions of the subcomponent's license, as noted in the LICENSE file.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user