Compare commits
14 Commits
main
...
issue/4462
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da7a62c2d0 | ||
|
|
e14e1f57db | ||
|
|
86d289c862 | ||
|
|
8d56a63016 | ||
|
|
e206d12f5c | ||
|
|
a4edb6cacc | ||
|
|
d2a0e739e8 | ||
|
|
53669b9e50 | ||
|
|
f21ca57dcd | ||
|
|
9c4072359a | ||
|
|
0eccd2794d | ||
|
|
1762491714 | ||
|
|
f60c529334 | ||
|
|
ce140e3deb |
2
pom.xml
2
pom.xml
@@ -5,7 +5,7 @@
|
||||
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>4.2.0-SNAPSHOT</version>
|
||||
<version>4.2.x-4462-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>Spring Data MongoDB</name>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>4.2.0-SNAPSHOT</version>
|
||||
<version>4.2.x-4462-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>4.2.0-SNAPSHOT</version>
|
||||
<version>4.2.x-4462-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>4.2.0-SNAPSHOT</version>
|
||||
<version>4.2.x-4462-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -87,6 +87,23 @@ public interface ExecutableUpdateOperation {
|
||||
T findAndModifyValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger <a href="https://docs.mongodb.com/manual/reference/method/db.collection.replaceOne/">replaceOne</a>
|
||||
* execution by calling one of the terminating methods.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 4.2
|
||||
*/
|
||||
interface TerminatingReplace {
|
||||
|
||||
/**
|
||||
* Find first and replace/upsert.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
UpdateResult replaceFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace</a>
|
||||
@@ -95,7 +112,7 @@ public interface ExecutableUpdateOperation {
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
interface TerminatingFindAndReplace<T> {
|
||||
interface TerminatingFindAndReplace<T> extends TerminatingReplace {
|
||||
|
||||
/**
|
||||
* Find, replace and return the first matching document.
|
||||
@@ -243,6 +260,22 @@ public interface ExecutableUpdateOperation {
|
||||
TerminatingFindAndModify<T> withOptions(FindAndModifyOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 4.2
|
||||
*/
|
||||
interface ReplaceWithOptions extends TerminatingReplace {
|
||||
|
||||
/**
|
||||
* Explicitly define {@link ReplaceOptions}.
|
||||
*
|
||||
* @param options must not be {@literal null}.
|
||||
* @return new instance of {@link FindAndReplaceOptions}.
|
||||
* @throws IllegalArgumentException if options is {@literal null}.
|
||||
*/
|
||||
TerminatingReplace withOptions(ReplaceOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define {@link FindAndReplaceOptions}.
|
||||
*
|
||||
@@ -250,7 +283,7 @@ public interface ExecutableUpdateOperation {
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
interface FindAndReplaceWithOptions<T> extends TerminatingFindAndReplace<T> {
|
||||
interface FindAndReplaceWithOptions<T> extends TerminatingFindAndReplace<T>, ReplaceWithOptions {
|
||||
|
||||
/**
|
||||
* Explicitly define {@link FindAndReplaceOptions} for the {@link Update}.
|
||||
|
||||
@@ -126,6 +126,17 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
options, replacement, targetType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminatingReplace withOptions(ReplaceOptions options) {
|
||||
|
||||
FindAndReplaceOptions target = new FindAndReplaceOptions();
|
||||
if (options.isUpsert()) {
|
||||
target.upsert();
|
||||
}
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
target, replacement, targetType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateWithUpdate<T> matching(Query query) {
|
||||
|
||||
@@ -175,6 +186,18 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
getCollectionName(), targetType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateResult replaceFirst() {
|
||||
|
||||
if (replacement != null) {
|
||||
return template.replace(query, domainType, replacement,
|
||||
findAndReplaceOptions != null ? findAndReplaceOptions : ReplaceOptions.none(), getCollectionName());
|
||||
}
|
||||
|
||||
return template.replace(query, domainType, update,
|
||||
findAndReplaceOptions != null ? findAndReplaceOptions : ReplaceOptions.none(), getCollectionName());
|
||||
}
|
||||
|
||||
private UpdateResult doUpdate(boolean multi, boolean upsert) {
|
||||
return template.doUpdate(getCollectionName(), query, update, domainType, upsert, multi);
|
||||
}
|
||||
|
||||
@@ -31,10 +31,9 @@ package org.springframework.data.mongodb.core;
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
public class FindAndReplaceOptions {
|
||||
public class FindAndReplaceOptions extends ReplaceOptions {
|
||||
|
||||
private boolean returnNew;
|
||||
private boolean upsert;
|
||||
|
||||
private static final FindAndReplaceOptions NONE = new FindAndReplaceOptions() {
|
||||
|
||||
@@ -109,7 +108,7 @@ public class FindAndReplaceOptions {
|
||||
*/
|
||||
public FindAndReplaceOptions upsert() {
|
||||
|
||||
this.upsert = true;
|
||||
super.upsert();
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -122,13 +121,4 @@ public class FindAndReplaceOptions {
|
||||
return returnNew;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bit indicating if to create a new document if not exists.
|
||||
*
|
||||
* @return {@literal true} if set.
|
||||
*/
|
||||
public boolean isUpsert() {
|
||||
return upsert;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,9 +21,10 @@ package org.springframework.data.mongodb.core;
|
||||
*
|
||||
* @author Mark Pollack
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
* @see MongoAction
|
||||
*/
|
||||
public enum MongoActionOperation {
|
||||
|
||||
REMOVE, UPDATE, INSERT, INSERT_LIST, SAVE, BULK;
|
||||
REMOVE, UPDATE, INSERT, INSERT_LIST, SAVE, BULK, REPLACE;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.springframework.data.mongodb.core.index.IndexOperations;
|
||||
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
|
||||
import org.springframework.data.mongodb.core.mapreduce.MapReduceResults;
|
||||
import org.springframework.data.mongodb.core.query.BasicQuery;
|
||||
import org.springframework.data.mongodb.core.query.Collation;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
@@ -118,7 +119,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
/**
|
||||
* Execute a MongoDB query and iterate over the query results on a per-document basis with a DocumentCallbackHandler.
|
||||
*
|
||||
* @param query the query class that specifies the criteria used to find a record and also an optional fields
|
||||
* @param query the query class that specifies the criteria used to find a document and also an optional fields
|
||||
* specification. Must not be {@literal null}.
|
||||
* @param collectionName name of the collection to retrieve the objects from.
|
||||
* @param dch the handler that will extract results, one document at a time.
|
||||
@@ -224,7 +225,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* <p>
|
||||
* Returns a {@link String} that wraps the Mongo DB {@link com.mongodb.client.FindIterable} that needs to be closed.
|
||||
*
|
||||
* @param query the query class that specifies the criteria used to find a record and also an optional fields
|
||||
* @param query the query class that specifies the criteria used to find a document and also an optional fields
|
||||
* specification. Must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @param <T> element return type
|
||||
@@ -240,7 +241,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* <p>
|
||||
* Returns a {@link Stream} that wraps the Mongo DB {@link com.mongodb.client.FindIterable} that needs to be closed.
|
||||
*
|
||||
* @param query the query class that specifies the criteria used to find a record and also an optional fields
|
||||
* @param query the query class that specifies the criteria used to find a document and also an optional fields
|
||||
* specification. Must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @param collectionName must not be {@literal null} or empty.
|
||||
@@ -722,7 +723,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more
|
||||
* feature rich {@link Query}.
|
||||
*
|
||||
* @param query the query class that specifies the criteria used to find a record and also an optional fields
|
||||
* @param query the query class that specifies the criteria used to find a document and also an optional fields
|
||||
* specification.
|
||||
* @param entityClass the parametrized type of the returned list.
|
||||
* @return the converted object.
|
||||
@@ -738,7 +739,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more
|
||||
* feature rich {@link Query}.
|
||||
*
|
||||
* @param query the query class that specifies the criteria used to find a record and also an optional fields
|
||||
* @param query the query class that specifies the criteria used to find a document and also an optional fields
|
||||
* specification.
|
||||
* @param entityClass the parametrized type of the returned list.
|
||||
* @param collectionName name of the collection to retrieve the objects from.
|
||||
@@ -752,7 +753,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* <strong>NOTE:</strong> Any additional support for query/field mapping, etc. is not available due to the lack of
|
||||
* domain type information. Use {@link #exists(Query, Class, String)} to get full type specific support.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the criteria used to find a record.
|
||||
* @param query the {@link Query} class that specifies the criteria used to find a document.
|
||||
* @param collectionName name of the collection to check for objects.
|
||||
* @return {@literal true} if the query yields a result.
|
||||
*/
|
||||
@@ -761,7 +762,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
/**
|
||||
* Determine result of given {@link Query} contains at least one element.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the criteria used to find a record.
|
||||
* @param query the {@link Query} class that specifies the criteria used to find a document.
|
||||
* @param entityClass the parametrized type.
|
||||
* @return {@literal true} if the query yields a result.
|
||||
*/
|
||||
@@ -770,7 +771,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
/**
|
||||
* Determine result of given {@link Query} contains at least one element.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the criteria used to find a record.
|
||||
* @param query the {@link Query} class that specifies the criteria used to find a document.
|
||||
* @param entityClass the parametrized type. Can be {@literal null}.
|
||||
* @param collectionName name of the collection to check for objects.
|
||||
* @return {@literal true} if the query yields a result.
|
||||
@@ -784,7 +785,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more
|
||||
* feature rich {@link Query}.
|
||||
*
|
||||
* @param query the query class that specifies the criteria used to find a record and also an optional fields
|
||||
* @param query the query class that specifies the criteria used to find a document and also an optional fields
|
||||
* specification. Must not be {@literal null}.
|
||||
* @param entityClass the parametrized type of the returned list. Must not be {@literal null}.
|
||||
* @return the List of converted objects.
|
||||
@@ -798,7 +799,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more
|
||||
* feature rich {@link Query}.
|
||||
*
|
||||
* @param query the query class that specifies the criteria used to find a record and also an optional fields
|
||||
* @param query the query class that specifies the criteria used to find a document and also an optional fields
|
||||
* specification. Must not be {@literal null}.
|
||||
* @param entityClass the parametrized type of the returned list. Must not be {@literal null}.
|
||||
* @param collectionName name of the collection to retrieve the objects from. Must not be {@literal null}.
|
||||
@@ -819,7 +820,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* sort properties} as MongoDB does not support criteria to reconstruct a query result from absent document fields or
|
||||
* {@code null} values through {@code $gt/$lt} operators.
|
||||
*
|
||||
* @param query the query class that specifies the criteria used to find a record and also an optional fields
|
||||
* @param query the query class that specifies the criteria used to find a document and also an optional fields
|
||||
* specification. Must not be {@literal null}.
|
||||
* @param entityType the parametrized type of the returned window.
|
||||
* @return the converted window.
|
||||
@@ -844,7 +845,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* sort properties} as MongoDB does not support criteria to reconstruct a query result from absent document fields or
|
||||
* {@code null} values through {@code $gt/$lt} operators.
|
||||
*
|
||||
* @param query the query class that specifies the criteria used to find a record and also an optional fields
|
||||
* @param query the query class that specifies the criteria used to find a document and also an optional fields
|
||||
* specification. Must not be {@literal null}.
|
||||
* @param entityType the parametrized type of the returned window.
|
||||
* @param collectionName name of the collection to retrieve the objects from.
|
||||
@@ -942,7 +943,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify </a>
|
||||
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} to apply on matching documents. Must not be {@literal null}.
|
||||
* @param entityClass the parametrized type. Must not be {@literal null}.
|
||||
@@ -958,7 +959,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify </a>
|
||||
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} to apply on matching documents. Must not be {@literal null}.
|
||||
* @param entityClass the parametrized type. Must not be {@literal null}.
|
||||
@@ -976,7 +977,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking
|
||||
* {@link FindAndModifyOptions} into account.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document and also an optional
|
||||
* fields specification.
|
||||
* @param update the {@link UpdateDefinition} to apply on matching documents.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information.
|
||||
@@ -996,7 +997,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking
|
||||
* {@link FindAndModifyOptions} into account.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} to apply on matching documents. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
@@ -1022,7 +1023,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* Options are defaulted to {@link FindAndReplaceOptions#empty()}. <br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@literal null}, if not found.
|
||||
@@ -1043,7 +1044,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* Options are defaulted to {@link FindAndReplaceOptions#empty()}. <br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param collectionName the collection to query. Must not be {@literal null}.
|
||||
@@ -1062,7 +1063,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* taking {@link FindAndReplaceOptions} into account.<br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
@@ -1085,7 +1086,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* taking {@link FindAndReplaceOptions} into account.<br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
@@ -1108,7 +1109,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* taking {@link FindAndReplaceOptions} into account.<br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
@@ -1133,7 +1134,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* taking {@link FindAndReplaceOptions} into account.<br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
@@ -1163,7 +1164,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* taking {@link FindAndReplaceOptions} into account.<br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
@@ -1188,7 +1189,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more
|
||||
* feature rich {@link Query}.
|
||||
*
|
||||
* @param query the query class that specifies the criteria used to find a record and also an optional fields
|
||||
* @param query the query class that specifies the criteria used to find a document and also an optional fields
|
||||
* specification.
|
||||
* @param entityClass the parametrized type of the returned list.
|
||||
* @return the converted object
|
||||
@@ -1205,7 +1206,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more
|
||||
* feature rich {@link Query}.
|
||||
*
|
||||
* @param query the query class that specifies the criteria used to find a record and also an optional fields
|
||||
* @param query the query class that specifies the criteria used to find a document and also an optional fields
|
||||
* specification.
|
||||
* @param entityClass the parametrized type of the returned list.
|
||||
* @param collectionName name of the collection to retrieve the objects from.
|
||||
@@ -1490,7 +1491,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* <strong>NOTE:</strong> {@link Query#getSortObject() sorting} is not supported by {@code db.collection.updateOne}.
|
||||
* Use {@link #findAndModify(Query, UpdateDefinition, FindAndModifyOptions, Class, String)} instead.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be upserted. Must not be
|
||||
* @param query the query document that specifies the criteria used to select a document to be upserted. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} operators to manipulate
|
||||
* the existing object. Must not be {@literal null}.
|
||||
@@ -1513,7 +1514,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* <strong>NOTE:</strong> {@link Query#getSortObject() sorting} is not supported by {@code db.collection.updateOne}.
|
||||
* Use {@link #findAndModify(Query, UpdateDefinition, FindAndModifyOptions, Class, String)} instead.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be upserted. Must not be
|
||||
* @param query the query document that specifies the criteria used to select a document to be upserted. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} operators to manipulate
|
||||
* the existing object. Must not be {@literal null}.
|
||||
@@ -1529,7 +1530,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* Performs an upsert. If no document is found that matches the query, a new document is created and inserted by
|
||||
* combining the query document and the update document.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be upserted. Must not be
|
||||
* @param query the query document that specifies the criteria used to select a document to be upserted. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} operators to manipulate
|
||||
* the existing object. Must not be {@literal null}.
|
||||
@@ -1546,7 +1547,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* Updates the first object that is found in the collection of the entity class that matches the query document with
|
||||
* the provided update document.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be updated. Must not be
|
||||
* @param query the query document that specifies the criteria used to select a document to be updated. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} operators to manipulate
|
||||
* the existing. Must not be {@literal null}.
|
||||
@@ -1569,7 +1570,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* <strong>NOTE:</strong> {@link Query#getSortObject() sorting} is not supported by {@code db.collection.updateOne}.
|
||||
* Use {@link #findAndModify(Query, UpdateDefinition, Class, String)} instead.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be updated. Must not be
|
||||
* @param query the query document that specifies the criteria used to select a document to be updated. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} operators to manipulate
|
||||
* the existing. Must not be {@literal null}.
|
||||
@@ -1585,7 +1586,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* Updates the first object that is found in the specified collection that matches the query document criteria with
|
||||
* the provided updated document. <br />
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be updated. Must not be
|
||||
* @param query the query document that specifies the criteria used to select a document to be updated. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} operators to manipulate
|
||||
* the existing. Must not be {@literal null}.
|
||||
@@ -1602,7 +1603,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* Updates all objects that are found in the collection for the entity class that matches the query document criteria
|
||||
* with the provided updated document.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be updated. Must not be
|
||||
* @param query the query document that specifies the criteria used to select a document to be updated. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} operators to manipulate
|
||||
* the existing. Must not be {@literal null}.
|
||||
@@ -1623,7 +1624,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* domain type information. Use {@link #updateMulti(Query, UpdateDefinition, Class, String)} to get full type specific
|
||||
* support.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be updated. Must not be
|
||||
* @param query the query document that specifies the criteria used to select a document to be updated. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} operators to manipulate
|
||||
* the existing. Must not be {@literal null}.
|
||||
@@ -1639,7 +1640,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* Updates all objects that are found in the collection for the entity class that matches the query document criteria
|
||||
* with the provided updated document.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be updated. Must not be
|
||||
* @param query the query document that specifies the criteria used to select a document to be updated. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} operators to manipulate
|
||||
* the existing. Must not be {@literal null}.
|
||||
@@ -1672,7 +1673,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* acknowledged} remove operation was successful or not.
|
||||
*
|
||||
* @param object must not be {@literal null}.
|
||||
* @param collectionName name of the collection where the objects will removed, must not be {@literal null} or empty.
|
||||
* @param collectionName name of the collection where the documents will be removed from, must not be {@literal null} or empty.
|
||||
* @return the {@link DeleteResult} which lets you access the results of the previous delete.
|
||||
*/
|
||||
DeleteResult remove(Object object, String collectionName);
|
||||
@@ -1681,7 +1682,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* Remove all documents that match the provided query document criteria from the collection used to store the
|
||||
* entityClass. The Class parameter is also used to help convert the Id of the object if it is present in the query.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to remove a record.
|
||||
* @param query the query document that specifies the criteria used to remove a document.
|
||||
* @param entityClass class that determines the collection to use.
|
||||
* @return the {@link DeleteResult} which lets you access the results of the previous delete.
|
||||
* @throws IllegalArgumentException when {@literal query} or {@literal entityClass} is {@literal null}.
|
||||
@@ -1694,9 +1695,9 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* Remove all documents that match the provided query document criteria from the collection used to store the
|
||||
* entityClass. The Class parameter is also used to help convert the Id of the object if it is present in the query.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to remove a record.
|
||||
* @param query the query document that specifies the criteria used to remove a document.
|
||||
* @param entityClass class of the pojo to be operated on. Can be {@literal null}.
|
||||
* @param collectionName name of the collection where the objects will removed, must not be {@literal null} or empty.
|
||||
* @param collectionName name of the collection where the documents will be removed from, must not be {@literal null} or empty.
|
||||
* @return the {@link DeleteResult} which lets you access the results of the previous delete.
|
||||
* @throws IllegalArgumentException when {@literal query}, {@literal entityClass} or {@literal collectionName} is
|
||||
* {@literal null}.
|
||||
@@ -1709,8 +1710,8 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* <strong>NOTE:</strong> Any additional support for field mapping is not available due to the lack of domain type
|
||||
* information. Use {@link #remove(Query, Class, String)} to get full type specific support.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to remove a record.
|
||||
* @param collectionName name of the collection where the objects will removed, must not be {@literal null} or empty.
|
||||
* @param query the query document that specifies the criteria used to remove a document.
|
||||
* @param collectionName name of the collection where the documents will be removed from, must not be {@literal null} or empty.
|
||||
* @return the {@link DeleteResult} which lets you access the results of the previous delete.
|
||||
* @throws IllegalArgumentException when {@literal query} or {@literal collectionName} is {@literal null}.
|
||||
*/
|
||||
@@ -1722,7 +1723,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* information. Use {@link #findAllAndRemove(Query, Class, String)} to get full type specific support.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to find and remove documents.
|
||||
* @param collectionName name of the collection where the objects will removed, must not be {@literal null} or empty.
|
||||
* @param collectionName name of the collection where the documents will be removed from, must not be {@literal null} or empty.
|
||||
* @return the {@link List} converted objects deleted by this operation.
|
||||
* @since 1.5
|
||||
*/
|
||||
@@ -1747,12 +1748,79 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to find and remove documents.
|
||||
* @param entityClass class of the pojo to be operated on.
|
||||
* @param collectionName name of the collection where the objects will removed, must not be {@literal null} or empty.
|
||||
* @param collectionName name of the collection where the documents will be removed from, must not be {@literal null} or empty.
|
||||
* @return the {@link List} converted objects deleted by this operation.
|
||||
* @since 1.5
|
||||
*/
|
||||
<T> List<T> findAllAndRemove(Query query, Class<T> entityClass, String collectionName);
|
||||
|
||||
/**
|
||||
* Replace a single document matching the {@link Criteria} of given {@link Query} with the {@code replacement}
|
||||
* document. <br />
|
||||
* The collection name is derived from the {@literal replacement} type. <br />
|
||||
* Options are defaulted to {@link ReplaceOptions#none()}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document. The query may
|
||||
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
|
||||
* to use. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
|
||||
* @throws org.springframework.data.mapping.MappingException if the collection name cannot be
|
||||
* {@link #getCollectionName(Class) derived} from the given replacement value.
|
||||
* @since 4.2
|
||||
*/
|
||||
default <T> UpdateResult replace(Query query, T replacement) {
|
||||
return replace(query, replacement, ReplaceOptions.none());
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace a single document matching the {@link Criteria} of given {@link Query} with the {@code replacement}
|
||||
* document. Options are defaulted to {@link ReplaceOptions#none()}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document. The query may
|
||||
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
|
||||
* to use. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param collectionName the collection to query. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
|
||||
* @since 4.2
|
||||
*/
|
||||
default <T> UpdateResult replace(Query query, T replacement, String collectionName) {
|
||||
return replace(query, replacement, ReplaceOptions.none(), collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace a single document matching the {@link Criteria} of given {@link Query} with the {@code replacement}
|
||||
* document taking {@link ReplaceOptions} into account.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document.The query may
|
||||
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
|
||||
* to use. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link ReplaceOptions} holding additional information. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
|
||||
* @throws org.springframework.data.mapping.MappingException if the collection name cannot be
|
||||
* {@link #getCollectionName(Class) derived} from the given replacement value.
|
||||
* @since 4.2
|
||||
*/
|
||||
default <T> UpdateResult replace(Query query, T replacement, ReplaceOptions options) {
|
||||
return replace(query, replacement, options, getCollectionName(ClassUtils.getUserClass(replacement)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace a single document matching the {@link Criteria} of given {@link Query} with the {@code replacement}
|
||||
* document taking {@link ReplaceOptions} into account.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document. The query may *
|
||||
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
|
||||
* to use. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link ReplaceOptions} holding additional information. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
|
||||
* @since 4.2
|
||||
*/
|
||||
<T> UpdateResult replace(Query query, T replacement, ReplaceOptions options, String collectionName);
|
||||
|
||||
/**
|
||||
* Returns the underlying {@link MongoConverter}.
|
||||
*
|
||||
|
||||
@@ -178,6 +178,7 @@ import com.mongodb.client.result.UpdateResult;
|
||||
* @author Anton Barkan
|
||||
* @author Bartłomiej Mazur
|
||||
* @author Michael Krog
|
||||
* @author Jakub Zurawa
|
||||
*/
|
||||
public class MongoTemplate
|
||||
implements MongoOperations, ApplicationContextAware, IndexOperationsProvider, ReadPreferenceAware {
|
||||
@@ -1310,7 +1311,7 @@ public class MongoTemplate
|
||||
|
||||
if (ObjectUtils.nullSafeEquals(WriteResultChecking.EXCEPTION, writeResultChecking)) {
|
||||
if (wc == null || wc.getWObject() == null
|
||||
|| (wc.getWObject() instanceof Number concern && concern.intValue() < 1)) {
|
||||
|| (wc.getWObject()instanceof Number concern && concern.intValue() < 1)) {
|
||||
return WriteConcern.ACKNOWLEDGED;
|
||||
}
|
||||
}
|
||||
@@ -1618,7 +1619,7 @@ public class MongoTemplate
|
||||
}
|
||||
}
|
||||
|
||||
collectionToUse.replaceOne(filter, replacement, new ReplaceOptions().upsert(true));
|
||||
collectionToUse.replaceOne(filter, replacement, new com.mongodb.client.model.ReplaceOptions().upsert(true));
|
||||
}
|
||||
return mapped.getId();
|
||||
});
|
||||
@@ -1749,7 +1750,7 @@ public class MongoTemplate
|
||||
}
|
||||
}
|
||||
|
||||
ReplaceOptions replaceOptions = updateContext.getReplaceOptions(entityClass);
|
||||
com.mongodb.client.model.ReplaceOptions replaceOptions = updateContext.getReplaceOptions(entityClass);
|
||||
return collection.replaceOne(filter, updateObj, replaceOptions);
|
||||
} else {
|
||||
return multi ? collection.updateMany(queryObj, updateObj, opts)
|
||||
@@ -2067,6 +2068,48 @@ public class MongoTemplate
|
||||
return doFindAndDelete(collectionName, query, entityClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> UpdateResult replace(Query query, T replacement, ReplaceOptions options, String collectionName){
|
||||
|
||||
Assert.notNull(replacement, "Replacement must not be null");
|
||||
return replace(query, (Class<T>) ClassUtils.getUserClass(replacement), replacement, options, collectionName);
|
||||
}
|
||||
|
||||
protected <S, T> UpdateResult replace(Query query, Class<S> entityType, T replacement, ReplaceOptions options,
|
||||
String collectionName) {
|
||||
|
||||
Assert.notNull(query, "Query must not be null");
|
||||
Assert.notNull(replacement, "Replacement must not be null");
|
||||
Assert.notNull(options, "Options must not be null Use ReplaceOptions#none() instead");
|
||||
Assert.notNull(entityType, "EntityType must not be null");
|
||||
Assert.notNull(collectionName, "CollectionName must not be null");
|
||||
|
||||
Assert.isTrue(query.getLimit() <= 1, "Query must not define a limit other than 1 ore none");
|
||||
Assert.isTrue(query.getSkip() <= 0, "Query must not define skip");
|
||||
|
||||
UpdateContext updateContext = queryOperations.replaceSingleContext(query,
|
||||
operations.forEntity(replacement).toMappedDocument(this.mongoConverter), options.isUpsert());
|
||||
|
||||
replacement = maybeCallBeforeConvert(replacement, collectionName);
|
||||
Document mappedReplacement = updateContext.getMappedUpdate(mappingContext.getPersistentEntity(entityType));
|
||||
maybeEmitEvent(new BeforeSaveEvent<>(replacement, mappedReplacement, collectionName));
|
||||
replacement = maybeCallBeforeSave(replacement, mappedReplacement, collectionName);
|
||||
|
||||
MongoAction action = new MongoAction(writeConcern, MongoActionOperation.REPLACE, collectionName, entityType,
|
||||
mappedReplacement, updateContext.getQueryObject());
|
||||
|
||||
UpdateResult result = doReplace(options, entityType, collectionName, updateContext,
|
||||
createCollectionPreparer(query, action), mappedReplacement);
|
||||
|
||||
if (result.wasAcknowledged()) {
|
||||
|
||||
maybeEmitEvent(new AfterSaveEvent<>(replacement, mappedReplacement, collectionName));
|
||||
maybeCallAfterSave(replacement, mappedReplacement, collectionName);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve and remove all documents matching the given {@code query} by calling {@link #find(Query, Class, String)}
|
||||
* and {@link #remove(Query, Class, String)}, whereas the {@link Query} for {@link #remove(Query, Class, String)} is
|
||||
@@ -2732,6 +2775,17 @@ public class MongoTemplate
|
||||
return CollectionPreparerDelegate.of(query);
|
||||
}
|
||||
|
||||
CollectionPreparer<MongoCollection<Document>> createCollectionPreparer(Query query, @Nullable MongoAction action) {
|
||||
CollectionPreparer<MongoCollection<Document>> collectionPreparer = createDelegate(query);
|
||||
if (action == null) {
|
||||
return collectionPreparer;
|
||||
}
|
||||
return collectionPreparer.andThen(collection -> {
|
||||
WriteConcern writeConcern = prepareWriteConcern(action);
|
||||
return writeConcern != null ? collection.withWriteConcern(writeConcern) : collection;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Customize this part for findAndReplace.
|
||||
*
|
||||
@@ -2767,6 +2821,24 @@ public class MongoTemplate
|
||||
collectionName);
|
||||
}
|
||||
|
||||
private <S> UpdateResult doReplace(ReplaceOptions options, Class<S> entityType, String collectionName,
|
||||
UpdateContext updateContext, CollectionPreparer<MongoCollection<Document>> collectionPreparer,
|
||||
Document replacement) {
|
||||
|
||||
MongoPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(entityType);
|
||||
|
||||
ReplaceCallback replaceCallback = new ReplaceCallback(collectionPreparer,
|
||||
updateContext.getMappedQuery(persistentEntity), replacement, updateContext.getReplaceOptions(entityType, it -> {
|
||||
it.upsert(options.isUpsert());
|
||||
}));
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug(String.format("replace one using query: %s for class: %s in collection: %s",
|
||||
serializeToJsonSafely(updateContext.getMappedQuery(persistentEntity)), entityType, collectionName));
|
||||
}
|
||||
|
||||
return execute(collectionName, replaceCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the id property of the saved object, if it's not set already.
|
||||
*
|
||||
@@ -3555,4 +3627,26 @@ public class MongoTemplate
|
||||
long countDocuments(CollectionPreparer collectionPreparer, String collection, Document filter,
|
||||
CountOptions options);
|
||||
}
|
||||
|
||||
private static class ReplaceCallback implements CollectionCallback<UpdateResult> {
|
||||
|
||||
private final CollectionPreparer<MongoCollection<Document>> collectionPreparer;
|
||||
private final Document query;
|
||||
private final Document update;
|
||||
private final com.mongodb.client.model.ReplaceOptions options;
|
||||
|
||||
ReplaceCallback(CollectionPreparer<MongoCollection<Document>> collectionPreparer, Document query, Document update,
|
||||
com.mongodb.client.model.ReplaceOptions options) {
|
||||
this.collectionPreparer = collectionPreparer;
|
||||
this.query = query;
|
||||
this.update = update;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateResult doInCollection(MongoCollection<Document> collection)
|
||||
throws MongoException, DataAccessException {
|
||||
return collectionPreparer.prepare(collection).replaceOne(query, update, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,6 +193,15 @@ class QueryOperations {
|
||||
return new UpdateContext(replacement, upsert);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param replacement the {@link MappedDocument mapped replacement} document.
|
||||
* @param upsert use {@literal true} to insert diff when no existing document found.
|
||||
* @return new instance of {@link UpdateContext}.
|
||||
*/
|
||||
UpdateContext replaceSingleContext(Query query, MappedDocument replacement, boolean upsert) {
|
||||
return new UpdateContext(query, replacement, upsert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link DeleteContext} instance removing all matching documents.
|
||||
*
|
||||
@@ -439,6 +448,25 @@ class QueryOperations {
|
||||
return entityOperations.forType(domainType).getCollation(query) //
|
||||
.map(Collation::toMongoCollation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link HintFunction} reading the actual hint form the {@link Query}.
|
||||
*
|
||||
* @return new instance of {@link HintFunction}.
|
||||
* @since 4.2
|
||||
*/
|
||||
HintFunction getHintFunction() {
|
||||
return HintFunction.from(query.getHint());
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and apply the hint from the {@link Query}.
|
||||
*
|
||||
* @since 4.2
|
||||
*/
|
||||
<R> void applyHint(Function<String, R> stringConsumer, Function<Bson, R> bsonConsumer) {
|
||||
getHintFunction().ifPresent(codecRegistryProvider, stringConsumer, bsonConsumer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -696,8 +724,12 @@ class QueryOperations {
|
||||
}
|
||||
|
||||
UpdateContext(MappedDocument update, boolean upsert) {
|
||||
this(new BasicQuery(BsonUtils.asDocument(update.getIdFilter())), update, upsert);
|
||||
}
|
||||
|
||||
super(new BasicQuery(BsonUtils.asDocument(update.getIdFilter())));
|
||||
UpdateContext(Query query, MappedDocument update, boolean upsert) {
|
||||
|
||||
super(query);
|
||||
this.multi = false;
|
||||
this.upsert = upsert;
|
||||
this.mappedDocument = update;
|
||||
@@ -765,6 +797,7 @@ class QueryOperations {
|
||||
ReplaceOptions options = new ReplaceOptions();
|
||||
options.collation(updateOptions.getCollation());
|
||||
options.upsert(updateOptions.isUpsert());
|
||||
applyHint(options::hintString, options::hint);
|
||||
|
||||
if (callback != null) {
|
||||
callback.accept(options);
|
||||
@@ -778,7 +811,7 @@ class QueryOperations {
|
||||
|
||||
Document mappedQuery = super.getMappedQuery(domainType);
|
||||
|
||||
if (multi && update.isIsolated() && !mappedQuery.containsKey("$isolated")) {
|
||||
if (multi && update != null && update.isIsolated() && !mappedQuery.containsKey("$isolated")) {
|
||||
mappedQuery.put("$isolated", 1);
|
||||
}
|
||||
|
||||
@@ -874,7 +907,7 @@ class QueryOperations {
|
||||
if (persistentEntity != null && persistentEntity.hasVersionProperty()) {
|
||||
|
||||
String versionFieldName = persistentEntity.getRequiredVersionProperty().getFieldName();
|
||||
if (!update.modifies(versionFieldName)) {
|
||||
if (update != null && !update.modifies(versionFieldName)) {
|
||||
update.inc(versionFieldName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import org.springframework.data.mongodb.core.query.Collation;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -417,7 +418,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more
|
||||
* feature rich {@link Query}.
|
||||
*
|
||||
* @param query the query class that specifies the criteria used to find a record and also an optional fields
|
||||
* @param query the query class that specifies the criteria used to find a document and also an optional fields
|
||||
* specification.
|
||||
* @param entityClass the parametrized type of the returned {@link Mono}.
|
||||
* @return the converted object.
|
||||
@@ -432,7 +433,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more
|
||||
* feature rich {@link Query}.
|
||||
*
|
||||
* @param query the query class that specifies the criteria used to find a record and also an optional fields
|
||||
* @param query the query class that specifies the criteria used to find a document and also an optional fields
|
||||
* specification.
|
||||
* @param entityClass the parametrized type of the returned {@link Mono}.
|
||||
* @param collectionName name of the collection to retrieve the objects from.
|
||||
@@ -445,7 +446,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* <strong>NOTE:</strong> Any additional support for query/field mapping, etc. is not available due to the lack of
|
||||
* domain type information. Use {@link #exists(Query, Class, String)} to get full type specific support.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the criteria used to find a record.
|
||||
* @param query the {@link Query} class that specifies the criteria used to find a document.
|
||||
* @param collectionName name of the collection to check for objects.
|
||||
* @return {@literal true} if the query yields a result.
|
||||
*/
|
||||
@@ -454,7 +455,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
/**
|
||||
* Determine result of given {@link Query} contains at least one element.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the criteria used to find a record.
|
||||
* @param query the {@link Query} class that specifies the criteria used to find a document.
|
||||
* @param entityClass the parametrized type.
|
||||
* @return {@literal true} if the query yields a result.
|
||||
*/
|
||||
@@ -463,7 +464,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
/**
|
||||
* Determine result of given {@link Query} contains at least one element.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the criteria used to find a record.
|
||||
* @param query the {@link Query} class that specifies the criteria used to find a document.
|
||||
* @param entityClass the parametrized type. Can be {@literal null}.
|
||||
* @param collectionName name of the collection to check for objects.
|
||||
* @return {@literal true} if the query yields a result.
|
||||
@@ -478,7 +479,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more
|
||||
* feature rich {@link Query}.
|
||||
*
|
||||
* @param query the query class that specifies the criteria used to find a record and also an optional fields
|
||||
* @param query the query class that specifies the criteria used to find a document and also an optional fields
|
||||
* specification. Must not be {@literal null}.
|
||||
* @param entityClass the parametrized type of the returned {@link Flux}. Must not be {@literal null}.
|
||||
* @return the {@link Flux} of converted objects.
|
||||
@@ -492,7 +493,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more
|
||||
* feature rich {@link Query}.
|
||||
*
|
||||
* @param query the query class that specifies the criteria used to find a record and also an optional fields
|
||||
* @param query the query class that specifies the criteria used to find a document and also an optional fields
|
||||
* specification. Must not be {@literal null}.
|
||||
* @param entityClass the parametrized type of the returned {@link Flux}.
|
||||
* @param collectionName name of the collection to retrieve the objects from. Must not be {@literal null}.
|
||||
@@ -513,7 +514,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* sort properties} as MongoDB does not support criteria to reconstruct a query result from absent document fields or
|
||||
* {@code null} values through {@code $gt/$lt} operators.
|
||||
*
|
||||
* @param query the query class that specifies the criteria used to find a record and also an optional fields
|
||||
* @param query the query class that specifies the criteria used to find a document and also an optional fields
|
||||
* specification. Must not be {@literal null}.
|
||||
* @param entityType the parametrized type of the returned list.
|
||||
* @return {@link Mono} emitting the converted window.
|
||||
@@ -538,7 +539,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* sort properties} as MongoDB does not support criteria to reconstruct a query result from absent document fields or
|
||||
* {@code null} values through {@code $gt/$lt} operators.
|
||||
*
|
||||
* @param query the query class that specifies the criteria used to find a record and also an optional fields
|
||||
* @param query the query class that specifies the criteria used to find a document and also an optional fields
|
||||
* specification. Must not be {@literal null}.
|
||||
* @param entityType the parametrized type of the returned list.
|
||||
* @param collectionName name of the collection to retrieve the objects from.
|
||||
@@ -758,7 +759,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify</a>
|
||||
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} to apply on matching documents. Must not be {@literal null}.
|
||||
* @param entityClass the parametrized type. Must not be {@literal null}.
|
||||
@@ -773,7 +774,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify</a>
|
||||
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} to apply on matching documents. Must not be {@literal null}.
|
||||
* @param entityClass the parametrized type. Must not be {@literal null}.
|
||||
@@ -790,7 +791,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking
|
||||
* {@link FindAndModifyOptions} into account.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document and also an optional
|
||||
* fields specification.
|
||||
* @param update the {@link UpdateDefinition} to apply on matching documents.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information.
|
||||
@@ -808,7 +809,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking
|
||||
* {@link FindAndModifyOptions} into account.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} to apply on matching documents. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
@@ -831,7 +832,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* Options are defaulted to {@link FindAndReplaceOptions#empty()}. <br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@link Mono#empty()}, if not found.
|
||||
@@ -851,7 +852,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* Options are defaulted to {@link FindAndReplaceOptions#empty()}. <br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param collectionName the collection to query. Must not be {@literal null}.
|
||||
@@ -869,7 +870,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* taking {@link FindAndReplaceOptions} into account. <br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
@@ -891,7 +892,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* taking {@link FindAndReplaceOptions} into account. <br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
@@ -913,7 +914,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* taking {@link FindAndReplaceOptions} into account. <br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
@@ -937,7 +938,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* taking {@link FindAndReplaceOptions} into account. <br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
@@ -966,7 +967,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* taking {@link FindAndReplaceOptions} into account. <br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
@@ -991,7 +992,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more
|
||||
* feature rich {@link Query}.
|
||||
*
|
||||
* @param query the query class that specifies the criteria used to find a record and also an optional fields
|
||||
* @param query the query class that specifies the criteria used to find a document and also an optional fields
|
||||
* specification.
|
||||
* @param entityClass the parametrized type of the returned {@link Mono}.
|
||||
* @return the converted object
|
||||
@@ -1007,7 +1008,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more
|
||||
* feature rich {@link Query}.
|
||||
*
|
||||
* @param query the query class that specifies the criteria used to find a record and also an optional fields
|
||||
* @param query the query class that specifies the criteria used to find a document and also an optional fields
|
||||
* specification.
|
||||
* @param entityClass the parametrized type of the returned {@link Mono}.
|
||||
* @param collectionName name of the collection to retrieve the objects from.
|
||||
@@ -1369,7 +1370,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* <strong>NOTE:</strong> {@link Query#getSortObject() sorting} is not supported by {@code db.collection.updateOne}.
|
||||
* Use {@link #findAndModify(Query, UpdateDefinition, Class)} instead.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be upserted. Must not be
|
||||
* @param query the query document that specifies the criteria used to select a document to be upserted. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} operators to manipulate
|
||||
* the existing object. Must not be {@literal null}.
|
||||
@@ -1390,7 +1391,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* domain type information. Use {@link #upsert(Query, UpdateDefinition, Class, String)} to get full type specific
|
||||
* support.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be upserted. Must not be
|
||||
* @param query the query document that specifies the criteria used to select a document to be upserted. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} operators to manipulate
|
||||
* the existing object. Must not be {@literal null}.
|
||||
@@ -1406,7 +1407,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* Performs an upsert. If no document is found that matches the query, a new document is created and inserted by
|
||||
* combining the query document and the update document.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be upserted. Must not be
|
||||
* @param query the query document that specifies the criteria used to select a document to be upserted. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} operators to manipulate
|
||||
* the existing object. Must not be {@literal null}.
|
||||
@@ -1425,7 +1426,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* <strong>NOTE:</strong> {@link Query#getSortObject() sorting} is not supported by {@code db.collection.updateOne}.
|
||||
* Use {@link #findAndModify(Query, UpdateDefinition, Class)} instead.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be updated. Must not be
|
||||
* @param query the query document that specifies the criteria used to select a document to be updated. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} operators to manipulate
|
||||
* the existing. Must not be {@literal null}.
|
||||
@@ -1448,7 +1449,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* <strong>NOTE:</strong> {@link Query#getSortObject() sorting} is not supported by {@code db.collection.updateOne}.
|
||||
* Use {@link #findAndModify(Query, UpdateDefinition, Class, String)} instead.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be updated. Must not be
|
||||
* @param query the query document that specifies the criteria used to select a document to be updated. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} operators to manipulate
|
||||
* the existing. Must not be {@literal null}.
|
||||
@@ -1464,7 +1465,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* Updates the first object that is found in the specified collection that matches the query document criteria with
|
||||
* the provided updated document. <br />
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be updated. Must not be
|
||||
* @param query the query document that specifies the criteria used to select a document to be updated. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} operators to manipulate
|
||||
* the existing. Must not be {@literal null}.
|
||||
@@ -1481,7 +1482,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* Updates all objects that are found in the collection for the entity class that matches the query document criteria
|
||||
* with the provided updated document.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be updated. Must not be
|
||||
* @param query the query document that specifies the criteria used to select a document to be updated. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} operators to manipulate
|
||||
* the existing. Must not be {@literal null}.
|
||||
@@ -1502,7 +1503,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* domain type information. Use {@link #updateMulti(Query, UpdateDefinition, Class, String)} to get full type specific
|
||||
* support.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be updated. Must not be
|
||||
* @param query the query document that specifies the criteria used to select a document to be updated. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} operators to manipulate
|
||||
* the existing. Must not be {@literal null}.
|
||||
@@ -1518,7 +1519,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* Updates all objects that are found in the collection for the entity class that matches the query document criteria
|
||||
* with the provided updated document.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be updated. Must not be
|
||||
* @param query the query document that specifies the criteria used to select a document to be updated. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} operators to manipulate
|
||||
* the existing. Must not be {@literal null}.
|
||||
@@ -1545,7 +1546,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* Removes the given object from the given collection.
|
||||
*
|
||||
* @param object must not be {@literal null}.
|
||||
* @param collectionName name of the collection where the objects will removed, must not be {@literal null} or empty.
|
||||
* @param collectionName name of the collection where the documents will be removed from, must not be {@literal null} or empty.
|
||||
* @return the {@link DeleteResult} which lets you access the results of the previous delete.
|
||||
*/
|
||||
Mono<DeleteResult> remove(Object object, String collectionName);
|
||||
@@ -1564,7 +1565,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* Removes the given object from the given collection.
|
||||
*
|
||||
* @param objectToRemove must not be {@literal null}.
|
||||
* @param collectionName name of the collection where the objects will removed, must not be {@literal null} or empty.
|
||||
* @param collectionName name of the collection where the documents will be removed from, must not be {@literal null} or empty.
|
||||
* @return the {@link DeleteResult} which lets you access the results of the previous delete.
|
||||
*/
|
||||
Mono<DeleteResult> remove(Mono<? extends Object> objectToRemove, String collectionName);
|
||||
@@ -1573,7 +1574,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* Remove all documents that match the provided query document criteria from the collection used to store the
|
||||
* entityClass. The Class parameter is also used to help convert the Id of the object if it is present in the query.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to remove a record.
|
||||
* @param query the query document that specifies the criteria used to remove a document.
|
||||
* @param entityClass class that determines the collection to use.
|
||||
* @return the {@link DeleteResult} which lets you access the results of the previous delete.
|
||||
* @throws org.springframework.data.mapping.MappingException if the target collection name cannot be
|
||||
@@ -1585,9 +1586,9 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* Remove all documents that match the provided query document criteria from the collection used to store the
|
||||
* entityClass. The Class parameter is also used to help convert the Id of the object if it is present in the query.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to remove a record.
|
||||
* @param query the query document that specifies the criteria used to remove a document.
|
||||
* @param entityClass class of the pojo to be operated on. Can be {@literal null}.
|
||||
* @param collectionName name of the collection where the objects will removed, must not be {@literal null} or empty.
|
||||
* @param collectionName name of the collection where the documents will be removed from, must not be {@literal null} or empty.
|
||||
* @return the {@link DeleteResult} which lets you access the results of the previous delete.
|
||||
*/
|
||||
Mono<DeleteResult> remove(Query query, @Nullable Class<?> entityClass, String collectionName);
|
||||
@@ -1598,8 +1599,8 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* <strong>NOTE:</strong> Any additional support for field mapping is not available due to the lack of domain type
|
||||
* information. Use {@link #remove(Query, Class, String)} to get full type specific support.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to remove a record.
|
||||
* @param collectionName name of the collection where the objects will removed, must not be {@literal null} or empty.
|
||||
* @param query the query document that specifies the criteria used to remove a document.
|
||||
* @param collectionName name of the collection where the documents will be removed from, must not be {@literal null} or empty.
|
||||
* @return the {@link DeleteResult} which lets you access the results of the previous delete.
|
||||
*/
|
||||
Mono<DeleteResult> remove(Query query, String collectionName);
|
||||
@@ -1610,7 +1611,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* information. Use {@link #findAllAndRemove(Query, Class, String)} to get full type specific support.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to find and remove documents.
|
||||
* @param collectionName name of the collection where the objects will removed, must not be {@literal null} or empty.
|
||||
* @param collectionName name of the collection where the documents will be removed from, must not be {@literal null} or empty.
|
||||
* @return the {@link Flux} converted objects deleted by this operation.
|
||||
*/
|
||||
<T> Flux<T> findAllAndRemove(Query query, String collectionName);
|
||||
@@ -1633,11 +1634,80 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to find and remove documents.
|
||||
* @param entityClass class of the pojo to be operated on.
|
||||
* @param collectionName name of the collection where the objects will removed, must not be {@literal null} or empty.
|
||||
* @param collectionName name of the collection where the documents will be removed from, must not be {@literal null} or empty.
|
||||
* @return the {@link Flux} converted objects deleted by this operation.
|
||||
*/
|
||||
<T> Flux<T> findAllAndRemove(Query query, Class<T> entityClass, String collectionName);
|
||||
|
||||
/**
|
||||
* Replace a single document matching the {@link Criteria} of given {@link Query} with the {@code replacement}
|
||||
* document. <br />
|
||||
* The collection name is derived from the {@literal replacement} type. <br />
|
||||
* Options are defaulted to {@link ReplaceOptions#none()}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document. The query may
|
||||
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
|
||||
* to use. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
|
||||
* @throws org.springframework.data.mapping.MappingException if the collection name cannot be
|
||||
* {@link #getCollectionName(Class) derived} from the given replacement value.
|
||||
* @since 4.2
|
||||
*/
|
||||
default <T> Mono<UpdateResult> replace(Query query, T replacement) {
|
||||
return replace(query, replacement, ReplaceOptions.none());
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace a single document matching the {@link Criteria} of given {@link Query} with the {@code replacement}
|
||||
* document. Options are defaulted to {@link ReplaceOptions#none()}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document. The query may
|
||||
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
|
||||
* to use. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param collectionName the collection to query. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
|
||||
* @since 4.2
|
||||
*/
|
||||
default <T> Mono<UpdateResult> replace(Query query, T replacement, String collectionName) {
|
||||
return replace(query, replacement, ReplaceOptions.none(), collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace a single document matching the {@link Criteria} of given {@link Query} with the {@code replacement}
|
||||
* document taking {@link ReplaceOptions} into account.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document.The query may
|
||||
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
|
||||
* to use. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link ReplaceOptions} holding additional information. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
|
||||
* @throws org.springframework.data.mapping.MappingException if the collection name cannot be
|
||||
* {@link #getCollectionName(Class) derived} from the given replacement value.
|
||||
* @since 4.2
|
||||
*/
|
||||
default <T> Mono<UpdateResult> replace(Query query, T replacement, ReplaceOptions options) {
|
||||
return replace(query, replacement, options, getCollectionName(ClassUtils.getUserClass(replacement)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace a single document matching the {@link Criteria} of given {@link Query} with the {@code replacement}
|
||||
* document taking {@link ReplaceOptions} into account.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document. The query may *
|
||||
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
|
||||
* to use. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link ReplaceOptions} holding additional information. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
|
||||
* @throws org.springframework.data.mapping.MappingException if the collection name cannot be
|
||||
* {@link #getCollectionName(Class) derived} from the given replacement value.
|
||||
* @since 4.2
|
||||
*/
|
||||
<T> Mono<UpdateResult> replace(Query query, T replacement, ReplaceOptions options, String collectionName);
|
||||
|
||||
/**
|
||||
* Map the results of an ad-hoc query on the collection for the entity class to a stream of objects of the specified
|
||||
* type. The stream uses a {@link com.mongodb.CursorType#TailableAwait tailable} cursor that may be an infinite
|
||||
@@ -1648,7 +1718,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more
|
||||
* feature rich {@link Query}.
|
||||
*
|
||||
* @param query the query class that specifies the criteria used to find a record and also an optional fields
|
||||
* @param query the query class that specifies the criteria used to find a document and also an optional fields
|
||||
* specification.
|
||||
* @param entityClass the parametrized type of the returned {@link Flux}.
|
||||
* @return the {@link Flux} of converted objects.
|
||||
@@ -1667,7 +1737,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more
|
||||
* feature rich {@link Query}.
|
||||
*
|
||||
* @param query the query class that specifies the criteria used to find a record and also an optional fields
|
||||
* @param query the query class that specifies the criteria used to find a document and also an optional fields
|
||||
* specification.
|
||||
* @param entityClass the parametrized type of the returned {@link Flux}.
|
||||
* @param collectionName name of the collection to retrieve the objects from.
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.data.mongodb.core;
|
||||
|
||||
import static org.springframework.data.mongodb.core.query.SerializationUtils.*;
|
||||
|
||||
import org.springframework.data.mongodb.core.CollectionPreparerSupport.CollectionPreparerDelegate;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.function.Tuple2;
|
||||
@@ -1782,7 +1783,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
deferredFilter = Mono.just(filter);
|
||||
}
|
||||
|
||||
ReplaceOptions replaceOptions = updateContext.getReplaceOptions(entityClass);
|
||||
com.mongodb.client.model.ReplaceOptions replaceOptions = updateContext.getReplaceOptions(entityClass);
|
||||
return deferredFilter.flatMap(it -> Mono.from(collectionToUse.replaceOne(it, updateObj, replaceOptions)));
|
||||
}
|
||||
|
||||
@@ -1959,6 +1960,34 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
return doFindAndDelete(collectionName, query, entityClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Mono<UpdateResult> replace(Query query, T replacement, ReplaceOptions options, String collectionName) {
|
||||
|
||||
Assert.notNull(replacement, "Replacement must not be null");
|
||||
return replace(query, (Class<T>) ClassUtils.getUserClass(replacement), replacement, options, collectionName);
|
||||
}
|
||||
|
||||
protected <S,T> Mono<UpdateResult> replace(Query query, Class<S> entityType, T replacement, ReplaceOptions options,
|
||||
String collectionName) {
|
||||
|
||||
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityType);
|
||||
UpdateContext updateContext = queryOperations.replaceSingleContext(query, operations.forEntity(replacement).toMappedDocument(this.mongoConverter), options.isUpsert());
|
||||
|
||||
return createMono(collectionName, collection -> {
|
||||
|
||||
Document mappedUpdate = updateContext.getMappedUpdate(entity);
|
||||
|
||||
MongoAction action = new MongoAction(writeConcern, MongoActionOperation.REPLACE, collectionName, entityType,
|
||||
mappedUpdate, updateContext.getQueryObject());
|
||||
|
||||
MongoCollection<Document> collectionToUse = createCollectionPreparer(query, action).prepare(collection);
|
||||
|
||||
return collectionToUse.replaceOne(updateContext.getMappedQuery(entity), mappedUpdate, updateContext.getReplaceOptions(entityType, it -> {
|
||||
it.upsert(options.isUpsert());
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Flux<T> tail(Query query, Class<T> entityClass) {
|
||||
return tail(query, entityClass, getCollectionName(entityClass));
|
||||
@@ -2341,6 +2370,21 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
objectCallback, collectionName);
|
||||
}
|
||||
|
||||
CollectionPreparer<MongoCollection<Document>> createCollectionPreparer(Query query) {
|
||||
return ReactiveCollectionPreparerDelegate.of(query);
|
||||
}
|
||||
|
||||
CollectionPreparer<MongoCollection<Document>> createCollectionPreparer(Query query, @Nullable MongoAction action) {
|
||||
CollectionPreparer<MongoCollection<Document>> collectionPreparer = createCollectionPreparer(query);
|
||||
if (action == null) {
|
||||
return collectionPreparer;
|
||||
}
|
||||
return collectionPreparer.andThen(collection -> {
|
||||
WriteConcern writeConcern = prepareWriteConcern(action);
|
||||
return writeConcern != null ? collection.withWriteConcern(writeConcern) : collection;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Map the results of an ad-hoc query on the default MongoDB collection to a List of the specified targetClass while
|
||||
* using sourceClass for mapping the query.
|
||||
|
||||
@@ -72,13 +72,30 @@ public interface ReactiveUpdateOperation {
|
||||
Mono<T> findAndModify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger <a href="https://docs.mongodb.com/manual/reference/method/db.collection.replaceOne/">replaceOne</a>
|
||||
* execution by calling one of the terminating methods.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 4.2
|
||||
*/
|
||||
interface TerminatingReplace {
|
||||
|
||||
/**
|
||||
* Find first and replace/upsert.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
Mono<UpdateResult> replaceFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose findAndReplace execution by calling one of the terminating methods.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
interface TerminatingFindAndReplace<T> {
|
||||
interface TerminatingFindAndReplace<T> extends TerminatingReplace {
|
||||
|
||||
/**
|
||||
* Find, replace and return the first matching document.
|
||||
@@ -202,6 +219,22 @@ public interface ReactiveUpdateOperation {
|
||||
TerminatingFindAndModify<T> withOptions(FindAndModifyOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 4.2
|
||||
*/
|
||||
interface ReplaceWithOptions extends TerminatingReplace {
|
||||
|
||||
/**
|
||||
* Explicitly define {@link ReplaceOptions}.
|
||||
*
|
||||
* @param options must not be {@literal null}.
|
||||
* @return new instance of {@link FindAndReplaceOptions}.
|
||||
* @throws IllegalArgumentException if options is {@literal null}.
|
||||
*/
|
||||
TerminatingReplace withOptions(ReplaceOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define {@link FindAndReplaceOptions}.
|
||||
*
|
||||
@@ -209,7 +242,7 @@ public interface ReactiveUpdateOperation {
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
interface FindAndReplaceWithOptions<T> extends TerminatingFindAndReplace<T> {
|
||||
interface FindAndReplaceWithOptions<T> extends TerminatingFindAndReplace<T>, ReplaceWithOptions {
|
||||
|
||||
/**
|
||||
* Explicitly define {@link FindAndReplaceOptions} for the {@link Update}.
|
||||
|
||||
@@ -165,6 +165,17 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
|
||||
replacement, targetType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminatingReplace withOptions(ReplaceOptions options) {
|
||||
|
||||
FindAndReplaceOptions target = new FindAndReplaceOptions();
|
||||
if (options.isUpsert()) {
|
||||
target.upsert();
|
||||
}
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
target, replacement, targetType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R> FindAndReplaceWithOptions<R> as(Class<R> resultType) {
|
||||
|
||||
@@ -174,6 +185,18 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
|
||||
findAndReplaceOptions, replacement, resultType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono <UpdateResult> replaceFirst() {
|
||||
|
||||
if (replacement != null) {
|
||||
return template.replace(query, domainType, replacement,
|
||||
findAndReplaceOptions != null ? findAndReplaceOptions : ReplaceOptions.none(), getCollectionName());
|
||||
}
|
||||
|
||||
return template.replace(query, domainType, update,
|
||||
findAndReplaceOptions != null ? findAndReplaceOptions : ReplaceOptions.none(), getCollectionName());
|
||||
}
|
||||
|
||||
private Mono<UpdateResult> doUpdate(boolean multi, boolean upsert) {
|
||||
return template.doUpdate(getCollectionName(), query, update, domainType, upsert, multi);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
|
||||
/**
|
||||
* Options for {@link org.springframework.data.mongodb.core.MongoOperations#replace(Query, Object) replace operations}. Defaults to
|
||||
* <dl>
|
||||
* <dt>upsert</dt>
|
||||
* <dd>false</dd>
|
||||
* </dl>
|
||||
*
|
||||
* @author Jakub Zurawa
|
||||
* @author Christoph Strob
|
||||
* @since 4.2
|
||||
*/
|
||||
public class ReplaceOptions {
|
||||
|
||||
private boolean upsert;
|
||||
|
||||
private static final ReplaceOptions NONE = new ReplaceOptions() {
|
||||
|
||||
private static final String ERROR_MSG = "ReplaceOptions.none() cannot be changed; Please use ReplaceOptions.options() instead";
|
||||
|
||||
@Override
|
||||
public ReplaceOptions upsert() {
|
||||
throw new UnsupportedOperationException(ERROR_MSG);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Static factory method to create a {@link ReplaceOptions} instance.
|
||||
* <dl>
|
||||
* <dt>upsert</dt>
|
||||
* <dd>false</dd>
|
||||
* </dl>
|
||||
*
|
||||
* @return new instance of {@link ReplaceOptions}.
|
||||
*/
|
||||
public static ReplaceOptions replaceOptions() {
|
||||
return new ReplaceOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Static factory method returning an unmodifiable {@link ReplaceOptions} instance.
|
||||
*
|
||||
* @return unmodifiable {@link ReplaceOptions} instance.
|
||||
*/
|
||||
public static ReplaceOptions none() {
|
||||
return NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new document if not exists.
|
||||
*
|
||||
* @return this.
|
||||
*/
|
||||
public ReplaceOptions upsert() {
|
||||
|
||||
this.upsert = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bit indicating if to create a new document if not exists.
|
||||
*
|
||||
* @return {@literal true} if set.
|
||||
*/
|
||||
public boolean isUpsert() {
|
||||
return upsert;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -247,6 +247,29 @@ class ExecutableUpdateOperationSupportTests {
|
||||
assertThat(result).isNotEqualTo(han).hasFieldOrPropertyWithValue("firstname", "Luke");
|
||||
}
|
||||
|
||||
@Test // GH-4463
|
||||
void replace() {
|
||||
|
||||
Person luke = new Person();
|
||||
luke.id = han.id;
|
||||
luke.firstname = "Luke";
|
||||
|
||||
UpdateResult result = template.update(Person.class).matching(queryHan()).replaceWith(luke).replaceFirst();
|
||||
assertThat(result.getModifiedCount()).isEqualTo(1L);
|
||||
}
|
||||
|
||||
@Test // GH-4463
|
||||
void replaceWithOptions() {
|
||||
|
||||
Person luke = new Person();
|
||||
luke.id = "upserted-luke";
|
||||
luke.firstname = "Luke";
|
||||
|
||||
UpdateResult result = template.update(Person.class).matching(query(where("firstname")
|
||||
.is("c3p0"))).replaceWith(luke).withOptions(ReplaceOptions.replaceOptions().upsert()).replaceFirst();
|
||||
assertThat(result.getUpsertedId()).isEqualTo(new BsonString("upserted-luke"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1827
|
||||
void findAndReplaceWithProjection() {
|
||||
|
||||
|
||||
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.springframework.data.mongodb.core.ReplaceOptions.*;
|
||||
import static org.springframework.data.mongodb.core.query.Criteria.*;
|
||||
import static org.springframework.data.mongodb.core.query.Query.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.bson.BsonInt64;
|
||||
import org.bson.Document;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.test.util.Client;
|
||||
import org.springframework.data.mongodb.test.util.MongoClientExtension;
|
||||
|
||||
import com.mongodb.client.MongoClient;
|
||||
import com.mongodb.client.MongoCollection;
|
||||
import com.mongodb.client.model.Filters;
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@ExtendWith(MongoClientExtension.class)
|
||||
public class MongoTemplateReplaceTests {
|
||||
|
||||
static final String DB_NAME = "mongo-template-replace-tests";
|
||||
static final String RESTAURANT_COLLECTION = "restaurant";
|
||||
|
||||
static @Client MongoClient client;
|
||||
private MongoTemplate template;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
|
||||
template = new MongoTemplate(client, DB_NAME);
|
||||
template.setEntityLifecycleEventsEnabled(false);
|
||||
|
||||
initTestData();
|
||||
}
|
||||
|
||||
@AfterEach()
|
||||
void afterEach() {
|
||||
clearTestData();
|
||||
}
|
||||
|
||||
@Test // GH-4462
|
||||
void replacesExistingDocument() {
|
||||
|
||||
UpdateResult result = template.replace(query(where("name").is("Central Perk Cafe")),
|
||||
new Restaurant("Central Pork Cafe", "Manhattan"));
|
||||
|
||||
assertThat(result.getMatchedCount()).isEqualTo(1);
|
||||
assertThat(result.getModifiedCount()).isEqualTo(1);
|
||||
|
||||
Document document = retrieve(collection -> collection.find(Filters.eq("_id", 1)).first());
|
||||
assertThat(document).containsEntry("r-name", "Central Pork Cafe");
|
||||
}
|
||||
|
||||
@Test // GH-4462
|
||||
void replacesFirstOnMoreThanOneMatch() {
|
||||
|
||||
UpdateResult result = template
|
||||
.replace(query(where("violations").exists(true)), new Restaurant("Central Pork Cafe", "Manhattan"));
|
||||
|
||||
assertThat(result.getMatchedCount()).isEqualTo(1);
|
||||
assertThat(result.getModifiedCount()).isEqualTo(1);
|
||||
|
||||
Document document = retrieve(collection -> collection.find(Filters.eq("_id", 2)).first());
|
||||
assertThat(document).containsEntry("r-name", "Central Pork Cafe");
|
||||
}
|
||||
|
||||
@Test // GH-4462
|
||||
void replacesExistingDocumentWithRawDoc() {
|
||||
|
||||
UpdateResult result = template.replace(query(where("r-name").is("Central Perk Cafe")),
|
||||
Document.parse("{ 'r-name' : 'Central Pork Cafe', 'Borough' : 'Manhattan' }"),
|
||||
template.getCollectionName(Restaurant.class));
|
||||
|
||||
assertThat(result.getMatchedCount()).isEqualTo(1);
|
||||
assertThat(result.getModifiedCount()).isEqualTo(1);
|
||||
|
||||
Document document = retrieve(collection -> collection.find(Filters.eq("_id", 1)).first());
|
||||
assertThat(document).containsEntry("r-name", "Central Pork Cafe");
|
||||
}
|
||||
|
||||
@Test // GH-4462
|
||||
void replacesExistingDocumentWithRawDocMappingQueryAgainstDomainType() {
|
||||
|
||||
UpdateResult result = template.replace(query(where("name").is("Central Perk Cafe")), Restaurant.class,
|
||||
Document.parse("{ 'r-name' : 'Central Pork Cafe', 'Borough' : 'Manhattan' }"), ReplaceOptions.none(), template.getCollectionName(Restaurant.class));
|
||||
|
||||
assertThat(result.getMatchedCount()).isEqualTo(1);
|
||||
assertThat(result.getModifiedCount()).isEqualTo(1);
|
||||
|
||||
Document document = retrieve(collection -> collection.find(Filters.eq("_id", 1)).first());
|
||||
assertThat(document).containsEntry("r-name", "Central Pork Cafe");
|
||||
}
|
||||
|
||||
@Test // GH-4462
|
||||
void replacesExistingDocumentWithMatchingId() {
|
||||
|
||||
UpdateResult result = template.replace(query(where("name").is("Central Perk Cafe")),
|
||||
new Restaurant(1L, "Central Pork Cafe", "Manhattan", 0));
|
||||
|
||||
assertThat(result.getMatchedCount()).isEqualTo(1);
|
||||
assertThat(result.getModifiedCount()).isEqualTo(1);
|
||||
|
||||
Document document = retrieve(collection -> collection.find(Filters.eq("_id", 1)).first());
|
||||
assertThat(document).containsEntry("r-name", "Central Pork Cafe");
|
||||
}
|
||||
|
||||
@Test // GH-4462
|
||||
void replacesExistingDocumentWithNewIdThrowsDataIntegrityViolationException() {
|
||||
|
||||
assertThatExceptionOfType(DataIntegrityViolationException.class)
|
||||
.isThrownBy(() -> template.replace(query(where("name").is("Central Perk Cafe")),
|
||||
new Restaurant(4L, "Central Pork Cafe", "Manhattan", 0)));
|
||||
}
|
||||
|
||||
@Test // GH-4462
|
||||
void doesNothingIfNoMatchFoundAndUpsertSetToFalse/* by default */() {
|
||||
|
||||
UpdateResult result = template.replace(query(where("name").is("Pizza Rat's Pizzaria")),
|
||||
new Restaurant(null, "Pizza Rat's Pizzaria", "Manhattan", 8));
|
||||
|
||||
assertThat(result.getMatchedCount()).isEqualTo(0);
|
||||
assertThat(result.getModifiedCount()).isEqualTo(0);
|
||||
|
||||
Document document = retrieve(collection -> collection.find(Filters.eq("r-name", "Pizza Rat's Pizzaria")).first());
|
||||
assertThat(document).isNull();
|
||||
}
|
||||
|
||||
@Test // GH-4462
|
||||
void insertsIfNoMatchFoundAndUpsertSetToTrue() {
|
||||
|
||||
UpdateResult result = template.replace(query(where("name").is("Pizza Rat's Pizzaria")),
|
||||
new Restaurant(4L, "Pizza Rat's Pizzaria", "Manhattan", 8), replaceOptions().upsert());
|
||||
|
||||
assertThat(result.getMatchedCount()).isEqualTo(0);
|
||||
assertThat(result.getModifiedCount()).isEqualTo(0);
|
||||
assertThat(result.getUpsertedId()).isEqualTo(new BsonInt64(4L));
|
||||
|
||||
Document document = retrieve(collection -> collection.find(Filters.eq("_id", 4)).first());
|
||||
assertThat(document).containsEntry("r-name", "Pizza Rat's Pizzaria");
|
||||
}
|
||||
|
||||
void initTestData() {
|
||||
|
||||
List<Document> testData = Stream.of( //
|
||||
"{ '_id' : 1, 'r-name' : 'Central Perk Cafe', 'Borough' : 'Manhattan' }",
|
||||
"{ '_id' : 2, 'r-name' : 'Rock A Feller Bar and Grill', 'Borough' : 'Queens', 'violations' : 2 }",
|
||||
"{ '_id' : 3, 'r-name' : 'Empire State Pub', 'Borough' : 'Brooklyn', 'violations' : 0 }") //
|
||||
.map(Document::parse).collect(Collectors.toList());
|
||||
|
||||
doInCollection(collection -> collection.insertMany(testData));
|
||||
}
|
||||
|
||||
void clearTestData() {
|
||||
doInCollection(collection -> collection.deleteMany(new Document()));
|
||||
}
|
||||
|
||||
void doInCollection(Consumer<MongoCollection<Document>> consumer) {
|
||||
retrieve(collection -> {
|
||||
consumer.accept(collection);
|
||||
return "done";
|
||||
});
|
||||
}
|
||||
|
||||
<T> T retrieve(Function<MongoCollection<Document>, T> fkt) {
|
||||
return fkt.apply(client.getDatabase(DB_NAME).getCollection(RESTAURANT_COLLECTION));
|
||||
}
|
||||
|
||||
@org.springframework.data.mongodb.core.mapping.Document(RESTAURANT_COLLECTION)
|
||||
static class Restaurant {
|
||||
|
||||
Long id;
|
||||
|
||||
@Field("r-name") String name;
|
||||
String borough;
|
||||
Integer violations;
|
||||
|
||||
Restaurant() {}
|
||||
|
||||
Restaurant(String name, String borough) {
|
||||
|
||||
this.name = name;
|
||||
this.borough = borough;
|
||||
}
|
||||
|
||||
Restaurant(Long id, String name, String borough, Integer violations) {
|
||||
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.borough = borough;
|
||||
this.violations = violations;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getRName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setRName(String rName) {
|
||||
this.name = rName;
|
||||
}
|
||||
|
||||
public String getBorough() {
|
||||
return borough;
|
||||
}
|
||||
|
||||
public void setBorough(String borough) {
|
||||
this.borough = borough;
|
||||
}
|
||||
|
||||
public int getViolations() {
|
||||
return violations;
|
||||
}
|
||||
|
||||
public void setViolations(int violations) {
|
||||
this.violations = violations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Restaurant{" + "id=" + id + ", name='" + name + '\'' + ", borough='" + borough + '\'' + ", violations="
|
||||
+ violations + '}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Restaurant that = (Restaurant) o;
|
||||
return violations == that.violations && Objects.equals(id, that.id) && Objects.equals(name, that.name)
|
||||
&& Objects.equals(borough, that.borough);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, name, borough, violations);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -116,6 +116,7 @@ import com.mongodb.client.result.UpdateResult;
|
||||
* @author Mark Paluch
|
||||
* @author Laszlo Csontos
|
||||
* @author duozhilin
|
||||
* @author Jakub Zurawa
|
||||
*/
|
||||
@ExtendWith(MongoClientExtension.class)
|
||||
public class MongoTemplateTests {
|
||||
@@ -3872,6 +3873,21 @@ public class MongoTemplateTests {
|
||||
assertThat(loaded).isEqualTo(source2);
|
||||
}
|
||||
|
||||
@Test // GH-4300
|
||||
public void replaceShouldReplaceDocument() {
|
||||
|
||||
org.bson.Document doc = new org.bson.Document("foo", "bar");
|
||||
String collectionName = "replace";
|
||||
template.save(doc, collectionName);
|
||||
|
||||
org.bson.Document replacement = new org.bson.Document("foo", "baz");
|
||||
UpdateResult updateResult = template.replace(query(where("foo").is("bar")), replacement, ReplaceOptions.replaceOptions(),
|
||||
collectionName);
|
||||
|
||||
assertThat(updateResult.wasAcknowledged()).isTrue();
|
||||
assertThat(template.findOne(query(where("foo").is("baz")), org.bson.Document.class, collectionName)).isNotNull();
|
||||
}
|
||||
|
||||
private AtomicReference<ImmutableVersioned> createAfterSaveReference() {
|
||||
|
||||
AtomicReference<ImmutableVersioned> saved = new AtomicReference<>();
|
||||
|
||||
@@ -131,6 +131,7 @@ import com.mongodb.client.result.UpdateResult;
|
||||
* @author Michael J. Simons
|
||||
* @author Roman Puchkovskiy
|
||||
* @author Yadhukrishna S Pai
|
||||
* @author Jakub Zurawa
|
||||
*/
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
@@ -173,10 +174,11 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
when(collection.aggregate(any(List.class), any())).thenReturn(aggregateIterable);
|
||||
when(collection.withReadConcern(any())).thenReturn(collection);
|
||||
when(collection.withReadPreference(any())).thenReturn(collection);
|
||||
when(collection.replaceOne(any(), any(), any(ReplaceOptions.class))).thenReturn(updateResult);
|
||||
when(collection.replaceOne(any(), any(), any(com.mongodb.client.model.ReplaceOptions.class))).thenReturn(updateResult);
|
||||
when(collection.withWriteConcern(any())).thenReturn(collectionWithWriteConcern);
|
||||
when(collection.distinct(anyString(), any(Document.class), any())).thenReturn(distinctIterable);
|
||||
when(collectionWithWriteConcern.deleteOne(any(Bson.class), any())).thenReturn(deleteResult);
|
||||
when(collectionWithWriteConcern.replaceOne(any(), any(), any(com.mongodb.client.model.ReplaceOptions.class))).thenReturn(updateResult);
|
||||
when(findIterable.projection(any())).thenReturn(findIterable);
|
||||
when(findIterable.sort(any(org.bson.Document.class))).thenReturn(findIterable);
|
||||
when(findIterable.collation(any())).thenReturn(findIterable);
|
||||
@@ -845,8 +847,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
@Test // GH-4277
|
||||
void findShouldUseReadConcernWhenPresent() {
|
||||
|
||||
template.find(new BasicQuery("{'foo' : 'bar'}").withReadConcern(ReadConcern.SNAPSHOT),
|
||||
AutogenerateableId.class);
|
||||
template.find(new BasicQuery("{'foo' : 'bar'}").withReadConcern(ReadConcern.SNAPSHOT), AutogenerateableId.class);
|
||||
|
||||
verify(collection).withReadConcern(ReadConcern.SNAPSHOT);
|
||||
}
|
||||
@@ -1002,7 +1003,8 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
|
||||
template.updateFirst(new BasicQuery("{}").collation(Collation.of("fr")), new Update(), AutogenerateableId.class);
|
||||
|
||||
ArgumentCaptor<ReplaceOptions> options = ArgumentCaptor.forClass(ReplaceOptions.class);
|
||||
ArgumentCaptor<com.mongodb.client.model.ReplaceOptions> options = ArgumentCaptor
|
||||
.forClass(com.mongodb.client.model.ReplaceOptions.class);
|
||||
verify(collection).replaceOne(any(), any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation().getLocale()).isEqualTo("fr");
|
||||
@@ -1129,8 +1131,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
void appliesFieldsWhenInterfaceProjectionIsClosedAndQueryDoesNotDefineFields() {
|
||||
|
||||
template.doFind(CollectionPreparer.identity(), "star-wars", new Document(), new Document(), Person.class,
|
||||
PersonProjection.class,
|
||||
CursorPreparer.NO_OP_PREPARER);
|
||||
PersonProjection.class, CursorPreparer.NO_OP_PREPARER);
|
||||
|
||||
verify(findIterable).projection(eq(new Document("firstname", 1)));
|
||||
}
|
||||
@@ -1139,8 +1140,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
void doesNotApplyFieldsWhenInterfaceProjectionIsClosedAndQueryDefinesFields() {
|
||||
|
||||
template.doFind(CollectionPreparer.identity(), "star-wars", new Document(), new Document("bar", 1), Person.class,
|
||||
PersonProjection.class,
|
||||
CursorPreparer.NO_OP_PREPARER);
|
||||
PersonProjection.class, CursorPreparer.NO_OP_PREPARER);
|
||||
|
||||
verify(findIterable).projection(eq(new Document("bar", 1)));
|
||||
}
|
||||
@@ -1149,8 +1149,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
void doesNotApplyFieldsWhenInterfaceProjectionIsOpen() {
|
||||
|
||||
template.doFind(CollectionPreparer.identity(), "star-wars", new Document(), new Document(), Person.class,
|
||||
PersonSpELProjection.class,
|
||||
CursorPreparer.NO_OP_PREPARER);
|
||||
PersonSpELProjection.class, CursorPreparer.NO_OP_PREPARER);
|
||||
|
||||
verify(findIterable).projection(eq(BsonUtils.EMPTY_DOCUMENT));
|
||||
}
|
||||
@@ -1159,8 +1158,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
void appliesFieldsToDtoProjection() {
|
||||
|
||||
template.doFind(CollectionPreparer.identity(), "star-wars", new Document(), new Document(), Person.class,
|
||||
Jedi.class,
|
||||
CursorPreparer.NO_OP_PREPARER);
|
||||
Jedi.class, CursorPreparer.NO_OP_PREPARER);
|
||||
|
||||
verify(findIterable).projection(eq(new Document("firstname", 1)));
|
||||
}
|
||||
@@ -1169,8 +1167,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
void doesNotApplyFieldsToDtoProjectionWhenQueryDefinesFields() {
|
||||
|
||||
template.doFind(CollectionPreparer.identity(), "star-wars", new Document(), new Document("bar", 1), Person.class,
|
||||
Jedi.class,
|
||||
CursorPreparer.NO_OP_PREPARER);
|
||||
Jedi.class, CursorPreparer.NO_OP_PREPARER);
|
||||
|
||||
verify(findIterable).projection(eq(new Document("bar", 1)));
|
||||
}
|
||||
@@ -1179,8 +1176,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
void doesNotApplyFieldsWhenTargetIsNotAProjection() {
|
||||
|
||||
template.doFind(CollectionPreparer.identity(), "star-wars", new Document(), new Document(), Person.class,
|
||||
Person.class,
|
||||
CursorPreparer.NO_OP_PREPARER);
|
||||
Person.class, CursorPreparer.NO_OP_PREPARER);
|
||||
|
||||
verify(findIterable).projection(eq(BsonUtils.EMPTY_DOCUMENT));
|
||||
}
|
||||
@@ -1189,8 +1185,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
void doesNotApplyFieldsWhenTargetExtendsDomainType() {
|
||||
|
||||
template.doFind(CollectionPreparer.identity(), "star-wars", new Document(), new Document(), Person.class,
|
||||
PersonExtended.class,
|
||||
CursorPreparer.NO_OP_PREPARER);
|
||||
PersonExtended.class, CursorPreparer.NO_OP_PREPARER);
|
||||
|
||||
verify(findIterable).projection(eq(BsonUtils.EMPTY_DOCUMENT));
|
||||
}
|
||||
@@ -1243,7 +1238,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
|
||||
template.save(entity);
|
||||
|
||||
verify(collection, times(1)).replaceOne(queryCaptor.capture(), updateCaptor.capture(), any(ReplaceOptions.class));
|
||||
verify(collection, times(1)).replaceOne(queryCaptor.capture(), updateCaptor.capture(), any(com.mongodb.client.model.ReplaceOptions.class));
|
||||
|
||||
assertThat(queryCaptor.getValue()).isEqualTo(new Document("_id", 1).append("version", 10));
|
||||
assertThat(updateCaptor.getValue())
|
||||
@@ -1785,7 +1780,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
|
||||
ArgumentCaptor<Document> captor = ArgumentCaptor.forClass(Document.class);
|
||||
|
||||
verify(collection).replaceOne(any(), captor.capture(), any(ReplaceOptions.class));
|
||||
verify(collection).replaceOne(any(), captor.capture(), any(com.mongodb.client.model.ReplaceOptions.class));
|
||||
assertThat(captor.getValue()).containsEntry("added-by", "callback");
|
||||
}
|
||||
|
||||
@@ -2005,7 +2000,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
@Test // DATAMONGO-2341
|
||||
void saveShouldAppendNonDefaultShardKeyToVersionedEntityIfNotPresentInFilter() {
|
||||
|
||||
when(collection.replaceOne(any(), any(), any(ReplaceOptions.class)))
|
||||
when(collection.replaceOne(any(), any(), any(com.mongodb.client.model.ReplaceOptions.class)))
|
||||
.thenReturn(UpdateResult.acknowledged(1, 1L, null));
|
||||
|
||||
template.save(new ShardedVersionedEntityWithNonDefaultShardKey("id-1", 1L, "AT", 4230));
|
||||
@@ -2093,7 +2088,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
@Test // DATAMONGO-2341
|
||||
void saveVersionedShouldProjectOnShardKeyWhenLoadingExistingDocument() {
|
||||
|
||||
when(collection.replaceOne(any(), any(), any(ReplaceOptions.class)))
|
||||
when(collection.replaceOne(any(), any(), any(com.mongodb.client.model.ReplaceOptions.class)))
|
||||
.thenReturn(UpdateResult.acknowledged(1, 1L, null));
|
||||
when(findIterable.first()).thenReturn(new Document("_id", "id-1").append("country", "US").append("userid", 4230));
|
||||
|
||||
@@ -2442,6 +2437,83 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
any(FindOneAndReplaceOptions.class));
|
||||
}
|
||||
|
||||
@Test // GH-4462
|
||||
void replaceShouldUseCollationWhenPresent() {
|
||||
|
||||
template.replace(new BasicQuery("{}").collation(Collation.of("fr")), new AutogenerateableId());
|
||||
|
||||
ArgumentCaptor<com.mongodb.client.model.ReplaceOptions> options = ArgumentCaptor
|
||||
.forClass(com.mongodb.client.model.ReplaceOptions.class);
|
||||
verify(collection).replaceOne(any(), any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().isUpsert()).isFalse();
|
||||
assertThat(options.getValue().getCollation().getLocale()).isEqualTo("fr");
|
||||
}
|
||||
|
||||
@Test // GH-4462
|
||||
void replaceShouldNotUpsertByDefault() {
|
||||
|
||||
template.replace(new BasicQuery("{}"), new Sith());
|
||||
|
||||
ArgumentCaptor<com.mongodb.client.model.ReplaceOptions> options = ArgumentCaptor
|
||||
.forClass(com.mongodb.client.model.ReplaceOptions.class);
|
||||
verify(collection).replaceOne(any(), any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().isUpsert()).isFalse();
|
||||
}
|
||||
|
||||
@Test // GH-4462
|
||||
void replaceShouldUpsert() {
|
||||
|
||||
template.replace(new BasicQuery("{}"), new Sith(), ReplaceOptions.replaceOptions().upsert());
|
||||
|
||||
ArgumentCaptor<com.mongodb.client.model.ReplaceOptions> options = ArgumentCaptor
|
||||
.forClass(com.mongodb.client.model.ReplaceOptions.class);
|
||||
verify(collection).replaceOne(any(), any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().isUpsert()).isTrue();
|
||||
}
|
||||
|
||||
@Test // GH-4462
|
||||
void replaceShouldUseDefaultCollationWhenPresent() {
|
||||
|
||||
template.replace(new BasicQuery("{}"), new Sith(), ReplaceOptions.replaceOptions());
|
||||
|
||||
ArgumentCaptor<com.mongodb.client.model.ReplaceOptions> options = ArgumentCaptor
|
||||
.forClass(com.mongodb.client.model.ReplaceOptions.class);
|
||||
verify(collection).replaceOne(any(), any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation().getLocale()).isEqualTo("de_AT");
|
||||
}
|
||||
|
||||
@Test // GH-4462
|
||||
void replaceShouldUseHintIfPresent() {
|
||||
|
||||
template.replace(new BasicQuery("{}").withHint("index-to-use"), new Sith(), ReplaceOptions.replaceOptions().upsert());
|
||||
|
||||
ArgumentCaptor<com.mongodb.client.model.ReplaceOptions> options = ArgumentCaptor
|
||||
.forClass(com.mongodb.client.model.ReplaceOptions.class);
|
||||
verify(collection).replaceOne(any(), any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getHintString()).isEqualTo("index-to-use");
|
||||
}
|
||||
|
||||
@Test // GH-4462
|
||||
void replaceShouldApplyWriteConcern() {
|
||||
|
||||
template.setWriteConcernResolver(new WriteConcernResolver() {
|
||||
public WriteConcern resolve(MongoAction action) {
|
||||
|
||||
assertThat(action.getMongoActionOperation()).isEqualTo(MongoActionOperation.REPLACE);
|
||||
return WriteConcern.UNACKNOWLEDGED;
|
||||
}
|
||||
});
|
||||
|
||||
template.replace(new BasicQuery("{}").withHint("index-to-use"), new Sith(), ReplaceOptions.replaceOptions().upsert());
|
||||
|
||||
verify(collection).withWriteConcern(eq(WriteConcern.UNACKNOWLEDGED));
|
||||
}
|
||||
|
||||
class AutogenerateableId {
|
||||
|
||||
@Id BigInteger id;
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.springframework.data.mongodb.core.ReplaceOptions.*;
|
||||
import static org.springframework.data.mongodb.core.query.Criteria.*;
|
||||
import static org.springframework.data.mongodb.core.query.Query.*;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.bson.BsonInt64;
|
||||
import org.bson.Document;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.test.util.Client;
|
||||
import org.springframework.data.mongodb.test.util.MongoClientExtension;
|
||||
|
||||
import com.mongodb.client.model.Filters;
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
import com.mongodb.reactivestreams.client.MongoClient;
|
||||
import com.mongodb.reactivestreams.client.MongoCollection;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@ExtendWith(MongoClientExtension.class)
|
||||
public class ReactiveMongoTemplateReplaceTests {
|
||||
|
||||
static final String DB_NAME = "mongo-template-replace-tests";
|
||||
static final String RESTAURANT_COLLECTION = "restaurant";
|
||||
|
||||
static @Client MongoClient client;
|
||||
private ReactiveMongoTemplate template;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
|
||||
template = new ReactiveMongoTemplate(client, DB_NAME);
|
||||
template.setEntityLifecycleEventsEnabled(false);
|
||||
|
||||
initTestData();
|
||||
}
|
||||
|
||||
@AfterEach()
|
||||
void afterEach() {
|
||||
clearTestData();
|
||||
}
|
||||
|
||||
@Test // GH-4462
|
||||
void replacesExistingDocument() {
|
||||
|
||||
Mono<UpdateResult> result = template.replace(query(where("name").is("Central Perk Cafe")),
|
||||
new Restaurant("Central Pork Cafe", "Manhattan"));
|
||||
|
||||
result.as(StepVerifier::create).consumeNextWith(it -> {
|
||||
assertThat(it.getMatchedCount()).isEqualTo(1);
|
||||
assertThat(it.getModifiedCount()).isEqualTo(1);
|
||||
}).verifyComplete();
|
||||
|
||||
retrieve(collection -> collection.find(Filters.eq("_id", 1)).first()).as(StepVerifier::create)
|
||||
.consumeNextWith(document -> {
|
||||
assertThat(document).containsEntry("r-name", "Central Pork Cafe");
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // GH-4462
|
||||
void replacesFirstOnMoreThanOneMatch() {
|
||||
|
||||
Mono<UpdateResult> result = template.replace(query(where("violations").exists(true)),
|
||||
new Restaurant("Central Pork Cafe", "Manhattan"));
|
||||
|
||||
result.as(StepVerifier::create).consumeNextWith(it -> {
|
||||
assertThat(it.getMatchedCount()).isEqualTo(1);
|
||||
assertThat(it.getModifiedCount()).isEqualTo(1);
|
||||
}).verifyComplete();
|
||||
|
||||
retrieve(collection -> collection.find(Filters.eq("_id", 2)).first()).as(StepVerifier::create)
|
||||
.consumeNextWith(document -> {
|
||||
assertThat(document).containsEntry("r-name", "Central Pork Cafe");
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // GH-4462
|
||||
void replacesExistingDocumentWithRawDoc() {
|
||||
|
||||
Mono<UpdateResult> result = template.replace(query(where("r-name").is("Central Perk Cafe")),
|
||||
Document.parse("{ 'r-name' : 'Central Pork Cafe', 'Borough' : 'Manhattan' }"),
|
||||
template.getCollectionName(Restaurant.class));
|
||||
|
||||
result.as(StepVerifier::create).consumeNextWith(it -> {
|
||||
assertThat(it.getMatchedCount()).isEqualTo(1);
|
||||
assertThat(it.getModifiedCount()).isEqualTo(1);
|
||||
}).verifyComplete();
|
||||
|
||||
retrieve(collection -> collection.find(Filters.eq("_id", 1)).first()).as(StepVerifier::create)
|
||||
.consumeNextWith(document -> {
|
||||
assertThat(document).containsEntry("r-name", "Central Pork Cafe");
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // GH-4462
|
||||
void replacesExistingDocumentWithRawDocMappingQueryAgainstDomainType() {
|
||||
|
||||
Mono<UpdateResult> result = template.replace(query(where("name").is("Central Perk Cafe")), Restaurant.class,
|
||||
Document.parse("{ 'r-name' : 'Central Pork Cafe', 'Borough' : 'Manhattan' }"), ReplaceOptions.none(), template.getCollectionName(Restaurant.class));
|
||||
|
||||
result.as(StepVerifier::create).consumeNextWith(it -> {
|
||||
assertThat(it.getMatchedCount()).isEqualTo(1);
|
||||
assertThat(it.getModifiedCount()).isEqualTo(1);
|
||||
}).verifyComplete();
|
||||
|
||||
retrieve(collection -> collection.find(Filters.eq("_id", 1)).first()).as(StepVerifier::create)
|
||||
.consumeNextWith(document -> {
|
||||
assertThat(document).containsEntry("r-name", "Central Pork Cafe");
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // GH-4462
|
||||
void replacesExistingDocumentWithMatchingId() {
|
||||
|
||||
Mono<UpdateResult> result = template.replace(query(where("name").is("Central Perk Cafe")),
|
||||
new Restaurant(1L, "Central Pork Cafe", "Manhattan", 0));
|
||||
|
||||
result.as(StepVerifier::create).consumeNextWith(it -> {
|
||||
assertThat(it.getMatchedCount()).isEqualTo(1);
|
||||
assertThat(it.getModifiedCount()).isEqualTo(1);
|
||||
}).verifyComplete();
|
||||
|
||||
retrieve(collection -> collection.find(Filters.eq("_id", 1)).first()).as(StepVerifier::create)
|
||||
.consumeNextWith(document -> {
|
||||
assertThat(document).containsEntry("r-name", "Central Pork Cafe");
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // GH-4462
|
||||
void replacesExistingDocumentWithNewIdThrowsDataIntegrityViolationException() {
|
||||
|
||||
template.replace(query(where("name").is("Central Perk Cafe")),
|
||||
new Restaurant(4L, "Central Pork Cafe", "Manhattan", 0))
|
||||
.as(StepVerifier::create)
|
||||
.expectError(DataIntegrityViolationException.class)
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test // GH-4462
|
||||
void doesNothingIfNoMatchFoundAndUpsertSetToFalse/* by default */() {
|
||||
|
||||
Mono<UpdateResult> result = template.replace(query(where("name").is("Pizza Rat's Pizzaria")),
|
||||
new Restaurant(null, "Pizza Rat's Pizzaria", "Manhattan", 8));
|
||||
|
||||
result.as(StepVerifier::create).consumeNextWith(it -> {
|
||||
assertThat(it.getMatchedCount()).isEqualTo(0);
|
||||
assertThat(it.getModifiedCount()).isEqualTo(0);
|
||||
}).verifyComplete();
|
||||
|
||||
retrieve(collection -> collection.find(Filters.eq("r-name", "Pizza Rat's Pizzaria")).first())
|
||||
.as(StepVerifier::create).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // GH-4462
|
||||
void insertsIfNoMatchFoundAndUpsertSetToTrue() {
|
||||
|
||||
Mono<UpdateResult> result = template.replace(query(where("name").is("Pizza Rat's Pizzaria")),
|
||||
new Restaurant(4L, "Pizza Rat's Pizzaria", "Manhattan", 8), replaceOptions().upsert());
|
||||
|
||||
result.as(StepVerifier::create).consumeNextWith(it -> {
|
||||
assertThat(it.getMatchedCount()).isEqualTo(0);
|
||||
assertThat(it.getModifiedCount()).isEqualTo(0);
|
||||
assertThat(it.getUpsertedId()).isEqualTo(new BsonInt64(4L));
|
||||
}).verifyComplete();
|
||||
|
||||
retrieve(collection -> collection.find(Filters.eq("_id", 4)).first()).as(StepVerifier::create)
|
||||
.consumeNextWith(document -> {
|
||||
assertThat(document).containsEntry("r-name", "Pizza Rat's Pizzaria");
|
||||
});
|
||||
}
|
||||
|
||||
void initTestData() {
|
||||
|
||||
List<Document> testData = Stream.of( //
|
||||
"{ '_id' : 1, 'r-name' : 'Central Perk Cafe', 'Borough' : 'Manhattan' }",
|
||||
"{ '_id' : 2, 'r-name' : 'Rock A Feller Bar and Grill', 'Borough' : 'Queens', 'violations' : 2 }",
|
||||
"{ '_id' : 3, 'r-name' : 'Empire State Pub', 'Borough' : 'Brooklyn', 'violations' : 0 }") //
|
||||
.map(Document::parse).collect(Collectors.toList());
|
||||
|
||||
doInCollection(collection -> collection.insertMany(testData));
|
||||
}
|
||||
|
||||
void clearTestData() {
|
||||
doInCollection(collection -> collection.deleteMany(new Document()));
|
||||
}
|
||||
|
||||
void doInCollection(Function<MongoCollection<Document>, Publisher<?>> fkt) {
|
||||
retrieve(collection -> Mono.from(fkt.apply(collection))).then().as(StepVerifier::create).verifyComplete();
|
||||
}
|
||||
|
||||
<T> Mono<T> retrieve(Function<MongoCollection<Document>, Publisher<T>> fkt) {
|
||||
return Mono.from(fkt.apply(client.getDatabase(DB_NAME).getCollection(RESTAURANT_COLLECTION)));
|
||||
}
|
||||
|
||||
@org.springframework.data.mongodb.core.mapping.Document(RESTAURANT_COLLECTION)
|
||||
static class Restaurant {
|
||||
|
||||
Long id;
|
||||
|
||||
@Field("r-name") String name;
|
||||
String borough;
|
||||
Integer violations;
|
||||
|
||||
Restaurant() {}
|
||||
|
||||
Restaurant(String name, String borough) {
|
||||
|
||||
this.name = name;
|
||||
this.borough = borough;
|
||||
}
|
||||
|
||||
Restaurant(Long id, String name, String borough, Integer violations) {
|
||||
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.borough = borough;
|
||||
this.violations = violations;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getRName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setRName(String rName) {
|
||||
this.name = rName;
|
||||
}
|
||||
|
||||
public String getBorough() {
|
||||
return borough;
|
||||
}
|
||||
|
||||
public void setBorough(String borough) {
|
||||
this.borough = borough;
|
||||
}
|
||||
|
||||
public int getViolations() {
|
||||
return violations;
|
||||
}
|
||||
|
||||
public void setViolations(int violations) {
|
||||
this.violations = violations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Restaurant{" + "id=" + id + ", name='" + name + '\'' + ", borough='" + borough + '\'' + ", violations="
|
||||
+ violations + '}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Restaurant that = (Restaurant) o;
|
||||
return violations == that.violations && Objects.equals(id, that.id) && Objects.equals(name, that.name)
|
||||
&& Objects.equals(borough, that.borough);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, name, borough, violations);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,6 +20,8 @@ import static org.mockito.Mockito.*;
|
||||
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
|
||||
import static org.springframework.data.mongodb.test.util.Assertions.assertThat;
|
||||
|
||||
import com.mongodb.WriteConcern;
|
||||
import org.springframework.data.mongodb.core.MongoTemplateUnitTests.Sith;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
@@ -1602,6 +1604,83 @@ public class ReactiveMongoTemplateUnitTests {
|
||||
verify(changeStreamPublisher).startAfter(eq(token));
|
||||
}
|
||||
|
||||
@Test // GH-4462
|
||||
void replaceShouldUseCollationWhenPresent() {
|
||||
|
||||
template.replace(new BasicQuery("{}").collation(Collation.of("fr")), new Jedi()).subscribe();
|
||||
|
||||
ArgumentCaptor<com.mongodb.client.model.ReplaceOptions> options = ArgumentCaptor
|
||||
.forClass(com.mongodb.client.model.ReplaceOptions.class);
|
||||
verify(collection).replaceOne(any(Bson.class), any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().isUpsert()).isFalse();
|
||||
assertThat(options.getValue().getCollation().getLocale()).isEqualTo("fr");
|
||||
}
|
||||
|
||||
@Test // GH-4462
|
||||
void replaceShouldNotUpsertByDefault() {
|
||||
|
||||
template.replace(new BasicQuery("{}"), new MongoTemplateUnitTests.Sith()).subscribe();
|
||||
|
||||
ArgumentCaptor<com.mongodb.client.model.ReplaceOptions> options = ArgumentCaptor
|
||||
.forClass(com.mongodb.client.model.ReplaceOptions.class);
|
||||
verify(collection).replaceOne(any(Bson.class), any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().isUpsert()).isFalse();
|
||||
}
|
||||
|
||||
@Test // GH-4462
|
||||
void replaceShouldUpsert() {
|
||||
|
||||
template.replace(new BasicQuery("{}"), new MongoTemplateUnitTests.Sith(), org.springframework.data.mongodb.core.ReplaceOptions.replaceOptions().upsert()).subscribe();
|
||||
|
||||
ArgumentCaptor<com.mongodb.client.model.ReplaceOptions> options = ArgumentCaptor
|
||||
.forClass(com.mongodb.client.model.ReplaceOptions.class);
|
||||
verify(collection).replaceOne(any(Bson.class), any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().isUpsert()).isTrue();
|
||||
}
|
||||
|
||||
@Test // GH-4462
|
||||
void replaceShouldUseDefaultCollationWhenPresent() {
|
||||
|
||||
template.replace(new BasicQuery("{}"), new MongoTemplateUnitTests.Sith(), org.springframework.data.mongodb.core.ReplaceOptions.replaceOptions()).subscribe();
|
||||
|
||||
ArgumentCaptor<com.mongodb.client.model.ReplaceOptions> options = ArgumentCaptor
|
||||
.forClass(com.mongodb.client.model.ReplaceOptions.class);
|
||||
verify(collection).replaceOne(any(Bson.class), any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation().getLocale()).isEqualTo("de_AT");
|
||||
}
|
||||
|
||||
@Test // GH-4462
|
||||
void replaceShouldUseHintIfPresent() {
|
||||
|
||||
template.replace(new BasicQuery("{}").withHint("index-to-use"), new MongoTemplateUnitTests.Sith(), org.springframework.data.mongodb.core.ReplaceOptions.replaceOptions().upsert()).subscribe();
|
||||
|
||||
ArgumentCaptor<com.mongodb.client.model.ReplaceOptions> options = ArgumentCaptor
|
||||
.forClass(com.mongodb.client.model.ReplaceOptions.class);
|
||||
verify(collection).replaceOne(any(Bson.class), any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getHintString()).isEqualTo("index-to-use");
|
||||
}
|
||||
|
||||
@Test // GH-4462
|
||||
void replaceShouldApplyWriteConcern() {
|
||||
|
||||
template.setWriteConcernResolver(new WriteConcernResolver() {
|
||||
public WriteConcern resolve(MongoAction action) {
|
||||
|
||||
assertThat(action.getMongoActionOperation()).isEqualTo(MongoActionOperation.REPLACE);
|
||||
return WriteConcern.UNACKNOWLEDGED;
|
||||
}
|
||||
});
|
||||
|
||||
template.replace(new BasicQuery("{}").withHint("index-to-use"), new Sith(), org.springframework.data.mongodb.core.ReplaceOptions.replaceOptions().upsert()).subscribe();
|
||||
|
||||
verify(collection).withWriteConcern(eq(WriteConcern.UNACKNOWLEDGED));
|
||||
}
|
||||
|
||||
private void stubFindSubscribe(Document document) {
|
||||
|
||||
Publisher<Document> realPublisher = Flux.just(document);
|
||||
|
||||
@@ -670,7 +670,7 @@ The query syntax used in the preceding example is explained in more detail in th
|
||||
[[mongo-template.id-handling]]
|
||||
=== How the `_id` Field is Handled in the Mapping Layer
|
||||
|
||||
MongoDB requires that you have an `_id` field for all documents. If you do not provide one, the driver assigns an `ObjectId` with a generated value. When you use the `MappingMongoConverter`, certain rules govern how properties from the Java class are mapped to this `_id` field:
|
||||
MongoDB requires that you have an `_id` field for all documents. If you do not provide one, the driver assigns an `ObjectId` with a generated value without considering your domain model as the server isn't aware of your identifier type. When you use the `MappingMongoConverter`, certain rules govern how properties from the Java class are mapped to this `_id` field:
|
||||
|
||||
. A property or field annotated with `@Id` (`org.springframework.data.annotation.Id`) maps to the `_id` field.
|
||||
. A property or field without an annotation but named `id` maps to the `_id` field.
|
||||
@@ -980,6 +980,55 @@ template.update(Person.class)
|
||||
|
||||
WARNING: `upsert` does not support ordering. Please use <<mongo-template.find-and-upsert, findAndModify>> to apply `Sort`.
|
||||
|
||||
[[mongo-template.replace]]
|
||||
=== Replacing Documents in a Collection
|
||||
|
||||
The various `replace` methods available via `MongoTemplate` allow to override the first matching Document.
|
||||
If no match is found a new one can be upserted (as outlined in the previous section) by providing `ReplaceOptions` with according configuration.
|
||||
|
||||
====
|
||||
.Replace one
|
||||
[source,java]
|
||||
----
|
||||
Person tom = template.insert(new Person("Motte", 21)); <1>
|
||||
|
||||
Query query = Query.query(Criteria.where("firstName").is(tom.getFirstName())); <2>
|
||||
|
||||
tom.setFirstname("Tom"); <3>
|
||||
template.replace(query, tom, ReplaceOptions.none()); <4>
|
||||
----
|
||||
<1> Insert a new document.
|
||||
<2> The query used to identify the single document to replace.
|
||||
<3> Set up the replacement document which must hold either the same `_id` as the existing or no `_id` at all.
|
||||
<4> Run the replace operation.
|
||||
|
||||
.Replace one with upsert
|
||||
[source,java]
|
||||
----
|
||||
Person tom = new Person("id-123", "Tom", 21) <1>
|
||||
|
||||
Query query = Query.query(Criteria.where("firstName").is(tom.getFirstName()));
|
||||
|
||||
template.replace(query, tom, ReplaceOptions.replaceOptions().upsert()); <2>
|
||||
----
|
||||
<1> The `_id` value needs to be provided for upsert, otherwise MongoDB will generate an `ObjectId`.
|
||||
As MongoDB is not aware of your domain type, any `@Field(targetType)` hints are not considered and the resulting `ObjectId` might be not compatible with your domain model.
|
||||
<2> Use `upsert` to insert a new document if no match is found.
|
||||
====
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
It is not possible to change the `_id` of existing documents with a replace operation.
|
||||
|
||||
On `upsert` MongoDB uses 2 ways of determining the new id for the entry:
|
||||
|
||||
* The `_id` is used within the query as in `{"_id" : 1234 }`
|
||||
* The `_id` is present in the replacement document.
|
||||
|
||||
If no `_id` is provided in either way, MongoDB will create a new `ObjectId` for the document.
|
||||
This may lead to mapping and data lookup malfunctions if the used domain types `id` property has a different type like e. g. `Long`.
|
||||
====
|
||||
|
||||
[[mongo-template.find-and-upsert]]
|
||||
=== Finding and Upserting Documents in a Collection
|
||||
|
||||
|
||||
Reference in New Issue
Block a user