diff --git a/src/main/antora/modules/ROOT/examples/MongoApplication.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/example/MongoApplication.java
similarity index 90%
rename from src/main/antora/modules/ROOT/examples/MongoApplication.java
rename to spring-data-mongodb/src/test/java/org/springframework/data/mongodb/example/MongoApplication.java
index b7cb8d3ff..ca6230f8b 100644
--- a/src/main/antora/modules/ROOT/examples/MongoApplication.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/example/MongoApplication.java
@@ -16,12 +16,10 @@
// tag::file[]
package org.springframework.data.mongodb.example;
-import static org.springframework.data.mongodb.core.query.Criteria.where;
-import static org.springframework.data.mongodb.core.query.Query.query;
+import static org.springframework.data.mongodb.core.query.Criteria.*;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
-import org.springframework.data.mongodb.core.query.Query;
import com.mongodb.client.MongoClients;
diff --git a/src/main/antora/modules/ROOT/examples/Person.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/example/Person.java
similarity index 100%
rename from src/main/antora/modules/ROOT/examples/Person.java
rename to spring-data-mongodb/src/test/java/org/springframework/data/mongodb/example/Person.java
diff --git a/src/main/antora/modules/ROOT/examples/ReactiveMongoApplication.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/example/ReactiveMongoApplication.java
similarity index 95%
rename from src/main/antora/modules/ROOT/examples/ReactiveMongoApplication.java
rename to spring-data-mongodb/src/test/java/org/springframework/data/mongodb/example/ReactiveMongoApplication.java
index cbb53c590..fbf4a2fa4 100644
--- a/src/main/antora/modules/ROOT/examples/ReactiveMongoApplication.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/example/ReactiveMongoApplication.java
@@ -17,7 +17,6 @@
package org.springframework.data.mongodb.example;
import static org.springframework.data.mongodb.core.query.Criteria.*;
-import static org.springframework.data.mongodb.core.query.Query.*;
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
diff --git a/src/main/antora/modules/ROOT/assets/images/epub-cover.png b/src/main/antora/modules/ROOT/assets/images/epub-cover.png
deleted file mode 100644
index 7be1d08b3..000000000
Binary files a/src/main/antora/modules/ROOT/assets/images/epub-cover.png and /dev/null differ
diff --git a/src/main/antora/modules/ROOT/assets/images/epub-cover.svg b/src/main/antora/modules/ROOT/assets/images/epub-cover.svg
deleted file mode 100644
index 6399a6094..000000000
--- a/src/main/antora/modules/ROOT/assets/images/epub-cover.svg
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
diff --git a/src/main/antora/modules/ROOT/examples/example b/src/main/antora/modules/ROOT/examples/example
new file mode 120000
index 000000000..3195fe72a
--- /dev/null
+++ b/src/main/antora/modules/ROOT/examples/example
@@ -0,0 +1 @@
+../../../../../../spring-data-mongodb/src/test/java/org/springframework/data/mongodb/example
\ No newline at end of file
diff --git a/src/main/antora/modules/ROOT/nav.adoc b/src/main/antora/modules/ROOT/nav.adoc
index 702566fdb..c5b207607 100644
--- a/src/main/antora/modules/ROOT/nav.adoc
+++ b/src/main/antora/modules/ROOT/nav.adoc
@@ -7,40 +7,40 @@
* xref:mongodb.adoc[]
** xref:mongodb/getting-started.adoc[]
** xref:mongodb/configuration.adoc[]
+
** xref:mongodb/template-api.adoc[]
*** xref:mongodb/template-config.adoc[]
+*** xref:mongodb/template-collection-management.adoc[]
*** xref:mongodb/template-crud-operations.adoc[]
*** xref:mongodb/template-query-operations.adoc[]
-**** xref:mongodb/template-query-options.adoc[]
-*** xref:mongodb/template-document-count.adoc[]
*** xref:mongodb/aggregation-framework.adoc[]
-*** xref:mongodb/template-collection-management.adoc[]
-**** xref:mongodb/template-collection-schema.adoc[]
-*** xref:mongodb/template-gridfs.adoc[]
+*** xref:mongodb/template-document-count.adoc[]
+
+** xref:mongodb/template-gridfs.adoc[]
** xref:mongodb/mapping/mapping.adoc[]
+*** xref:mongodb/mapping/mapping-schema.adoc[]
*** xref:mongodb/mapping/custom-conversions.adoc[Type based Converter]
*** xref:mongodb/mapping/property-converters.adoc[]
*** xref:mongodb/mapping/unwrapping-entities.adoc[]
*** xref:mongodb/mapping/document-references.adoc[Object References]
-*** xref:mongodb/mapping/entity-callbacks.adoc[Entity Callbacks]
-*** xref:mongodb/mapping/lifecycle-events.adoc[]
*** xref:mongodb/mapping/mapping-index-management.adoc[]
+
+** xref:mongodb/lifecycle-events.adoc[]
+** xref:mongodb/auditing.adoc[]
** xref:mongodb/client-session-transactions.adoc[]
** xref:mongodb/change-streams.adoc[]
** xref:mongodb/tailable-cursors.adoc[]
** xref:mongodb/sharding.adoc[]
** xref:mongodb/mongo-encryption.adoc[]
-** xref:mongodb/auditing.adoc[]
// Repository
* xref:repositories.adoc[]
** xref:repositories/core-concepts.adoc[]
** xref:repositories/definition.adoc[]
** xref:mongodb/repositories/repositories.adoc[]
-*** xref:mongodb/repositories/repositories-index-hints.adoc[]
-*** xref:mongodb/repositories/repositories-collation.adoc[]
** xref:repositories/create-instances.adoc[]
** xref:repositories/query-methods-details.adoc[]
+** xref:mongodb/repositories/query-methods.adoc[]
** xref:repositories/projections.adoc[]
** xref:repositories/custom-implementations.adoc[]
** xref:repositories/core-domain-events.adoc[]
diff --git a/src/main/antora/modules/ROOT/pages/kotlin/object-mapping.adoc b/src/main/antora/modules/ROOT/pages/kotlin/object-mapping.adoc
deleted file mode 100644
index 76cab88e9..000000000
--- a/src/main/antora/modules/ROOT/pages/kotlin/object-mapping.adoc
+++ /dev/null
@@ -1 +0,0 @@
-// include::{commons}@data-commons::page$kotlin/object-mapping.adoc[]
diff --git a/src/main/antora/modules/ROOT/pages/mongodb.adoc b/src/main/antora/modules/ROOT/pages/mongodb.adoc
index eebf9a914..de07ceb92 100644
--- a/src/main/antora/modules/ROOT/pages/mongodb.adoc
+++ b/src/main/antora/modules/ROOT/pages/mongodb.adoc
@@ -4,17 +4,18 @@
Spring Data support for MongoDB contains a wide range of features:
-* Spring configuration support with Java-based `@Configuration` classes or an XML namespace for a Mongo driver instance and replica sets.
-* `MongoTemplate` helper class that increases productivity when performing common Mongo operations. Includes integrated object mapping between documents and POJOs.
-* Exception translation into Spring's portable Data Access Exception hierarchy.
-* Feature-rich Object Mapping integrated with Spring's Conversion Service.
-* Annotation-based mapping metadata that is extensible to support other metadata formats.
-* Persistence and mapping lifecycle events.
-* Java-based Query, Criteria, and Update DSLs.
-* Automatic implementation of Repository interfaces, including support for custom finder methods.
-* QueryDSL integration to support type-safe queries.
-* Multi Document Transactions.
-* GeoSpatial integration.
+* xref:mongodb/template-config.adoc[Spring configuration support] with Java-based `@Configuration` classes or an XML namespace for a Mongo driver instance and replica sets.
+* xref:mongodb/template-api.adoc[`MongoTemplate` helper class] that increases productivity when performing common Mongo operations.
+Includes integrated object mapping between documents and POJOs.
+* xref:mongodb/template-api.adoc#mongo-template.exception-translation[Exception translation] into Spring's portable Data Access Exception hierarchy.
+* Feature-rich xref:mongodb/mapping/mapping.adoc[Object Mapping] integrated with Spring's Conversion Service.
+* xref:mongodb/mapping/mapping.adoc#mapping-usage-annotations[Annotation-based mapping metadata] that is extensible to support other metadata formats.
+* xref:mongodb/lifecycle-events.adoc[Persistence and mapping lifecycle events].
+* xref:mongodb/template-query-operations.adoc[Java-based Query, Criteria, and Update DSLs].
+* Automatic implementation of xref:repositories.adoc[Repository interfaces], including support for custom query methods.
+* xref:mongodb/repositories/repositories.adoc#mongodb.repositories.queries.type-safe[QueryDSL integration] to support type-safe queries.
+* xref:mongodb/client-session-transactions.adoc[Multi-Document Transactions].
+* xref:mongodb/template-query-operations.adoc#mongo.geo-json[GeoSpatial integration].
For most tasks, you should use `MongoTemplate` or the Repository support, which both leverage the rich mapping functionality.
`MongoTemplate` is the place to look for accessing functionality such as incrementing counters or ad-hoc CRUD operations.
diff --git a/src/main/antora/modules/ROOT/pages/mongodb/getting-started.adoc b/src/main/antora/modules/ROOT/pages/mongodb/getting-started.adoc
index e12fdc2a4..eed3a95e3 100644
--- a/src/main/antora/modules/ROOT/pages/mongodb/getting-started.adoc
+++ b/src/main/antora/modules/ROOT/pages/mongodb/getting-started.adoc
@@ -19,7 +19,7 @@ Then you can create a `Person` class to persist:
====
[source,java]
----
-include::example$Person.java[tags=file]
+include::example$example/Person.java[tags=file]
----
====
@@ -31,14 +31,14 @@ Imperative::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
-include::example$MongoApplication.java[tags=file]
+include::example$example/MongoApplication.java[tags=file]
----
Reactive::
+
[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
----
-include::example$ReactiveMongoApplication.java[tags=file]
+include::example$example/ReactiveMongoApplication.java[tags=file]
----
======
diff --git a/src/main/antora/modules/ROOT/pages/mongodb/mapping/lifecycle-events.adoc b/src/main/antora/modules/ROOT/pages/mongodb/lifecycle-events.adoc
similarity index 64%
rename from src/main/antora/modules/ROOT/pages/mongodb/mapping/lifecycle-events.adoc
rename to src/main/antora/modules/ROOT/pages/mongodb/lifecycle-events.adoc
index 8050b50c6..d6c608b40 100644
--- a/src/main/antora/modules/ROOT/pages/mongodb/mapping/lifecycle-events.adoc
+++ b/src/main/antora/modules/ROOT/pages/mongodb/lifecycle-events.adoc
@@ -49,8 +49,58 @@ Declaring these beans in your Spring ApplicationContext causes them to be invoke
* `onAfterConvert`: Called in `MongoTemplate` `find`, `findAndRemove`, `findOne`, and `getCollection` methods after the `Document` has been retrieved from the database was converted to a POJO.
====
-NOTE: Lifecycle events are only emitted for root level types. Complex types used as properties within a document root are not subject to event publication unless they are document references annotated with `@DBRef`.
+NOTE: Lifecycle events are only emitted for root level types.
+Complex types used as properties within a document root are not subject to event publication unless they are document references annotated with `@DBRef`.
WARNING: Lifecycle events depend on an `ApplicationEventMulticaster`, which in case of the `SimpleApplicationEventMulticaster` can be configured with a `TaskExecutor`, and therefore gives no guarantees when an Event is processed.
+include::{commons}@data-commons::page$entity-callbacks.adoc[leveloffset=+1]
+
+[[mongo.entity-callbacks]]
+== Store specific EntityCallbacks
+
+Spring Data MongoDB uses the `EntityCallback` API for its auditing support and reacts on the following callbacks.
+
+.Supported Entity Callbacks
+[%header,cols="4"]
+|===
+| Callback
+| Method
+| Description
+| Order
+
+| `ReactiveBeforeConvertCallback`
+`BeforeConvertCallback`
+| `onBeforeConvert(T entity, String collection)`
+| Invoked before a domain object is converted to `org.bson.Document`.
+| `Ordered.LOWEST_PRECEDENCE`
+
+| `ReactiveAfterConvertCallback`
+`AfterConvertCallback`
+| `onAfterConvert(T entity, org.bson.Document target, String collection)`
+| Invoked after a domain object is loaded. +
+Can modify the domain object after reading it from a `org.bson.Document`.
+| `Ordered.LOWEST_PRECEDENCE`
+
+| `ReactiveAuditingEntityCallback`
+`AuditingEntityCallback`
+| `onBeforeConvert(Object entity, String collection)`
+| Marks an auditable entity _created_ or _modified_
+| 100
+
+| `ReactiveBeforeSaveCallback`
+`BeforeSaveCallback`
+| `onBeforeSave(T entity, org.bson.Document target, String collection)`
+| Invoked before a domain object is saved. +
+Can modify the target, to be persisted, `Document` containing all mapped entity information.
+| `Ordered.LOWEST_PRECEDENCE`
+
+| `ReactiveAfterSaveCallback`
+`AfterSaveCallback`
+| `onAfterSave(T entity, org.bson.Document target, String collection)`
+| Invoked before a domain object is saved. +
+Can modify the domain object, to be returned after save, `Document` containing all mapped entity information.
+| `Ordered.LOWEST_PRECEDENCE`
+
+|===
diff --git a/src/main/antora/modules/ROOT/pages/mongodb/mapping/custom-conversions.adoc b/src/main/antora/modules/ROOT/pages/mongodb/mapping/custom-conversions.adoc
index 7fcc0fa14..c929fe2ad 100644
--- a/src/main/antora/modules/ROOT/pages/mongodb/mapping/custom-conversions.adoc
+++ b/src/main/antora/modules/ROOT/pages/mongodb/mapping/custom-conversions.adoc
@@ -30,7 +30,7 @@ public class Payment {
"date" : ISODate("2019-04-03T12:11:01.870Z") <3>
}
----
-<1> String _id_ values that represent a valid `ObjectId` are converted automatically. See xref:mongodb/template-id-handling.adoc#mongo-template.id-handling[How the `_id` Field is Handled in the Mapping Layer]
+<1> String _id_ values that represent a valid `ObjectId` are converted automatically. See xref:mongodb/template-crud-operations.adoc#mongo-template.id-handling[How the `_id` Field is Handled in the Mapping Layer]
for details.
<2> The desired target type is explicitly defined as `Decimal128` which translates to `NumberDecimal`. Otherwise the
`BigDecimal` value would have been truned into a `String`.
diff --git a/src/main/antora/modules/ROOT/pages/mongodb/mapping/entity-callbacks.adoc b/src/main/antora/modules/ROOT/pages/mongodb/mapping/entity-callbacks.adoc
deleted file mode 100644
index 3ae3cb86f..000000000
--- a/src/main/antora/modules/ROOT/pages/mongodb/mapping/entity-callbacks.adoc
+++ /dev/null
@@ -1,45 +0,0 @@
-include::{commons}@data-commons::page$entity-callbacks.adoc[]
-
-[[mongo.entity-callbacks]]
-== Store specific EntityCallbacks
-
-Spring Data MongoDB uses the `EntityCallback` API for its auditing support and reacts on the following callbacks.
-
-.Supported Entity Callbacks
-[%header,cols="4"]
-|===
-| Callback
-| Method
-| Description
-| Order
-
-| Reactive/BeforeConvertCallback
-| `onBeforeConvert(T entity, String collection)`
-| Invoked before a domain object is converted to `org.bson.Document`.
-| `Ordered.LOWEST_PRECEDENCE`
-
-| Reactive/AfterConvertCallback
-| `onAfterConvert(T entity, org.bson.Document target, String collection)`
-| Invoked after a domain object is loaded. +
-Can modify the domain object after reading it from a `org.bson.Document`.
-| `Ordered.LOWEST_PRECEDENCE`
-
-| Reactive/AuditingEntityCallback
-| `onBeforeConvert(Object entity, String collection)`
-| Marks an auditable entity _created_ or _modified_
-| 100
-
-| Reactive/BeforeSaveCallback
-| `onBeforeSave(T entity, org.bson.Document target, String collection)`
-| Invoked before a domain object is saved. +
- Can modify the target, to be persisted, `Document` containing all mapped entity information.
-| `Ordered.LOWEST_PRECEDENCE`
-
-| Reactive/AfterSaveCallback
-| `onAfterSave(T entity, org.bson.Document target, String collection)`
-| Invoked before a domain object is saved. +
- Can modify the domain object, to be returned after save, `Document` containing all mapped entity information.
-| `Ordered.LOWEST_PRECEDENCE`
-
-|===
-
diff --git a/src/main/antora/modules/ROOT/pages/mongodb/template-collection-schema.adoc b/src/main/antora/modules/ROOT/pages/mongodb/mapping/mapping-schema.adoc
similarity index 100%
rename from src/main/antora/modules/ROOT/pages/mongodb/template-collection-schema.adoc
rename to src/main/antora/modules/ROOT/pages/mongodb/mapping/mapping-schema.adoc
diff --git a/src/main/antora/modules/ROOT/pages/mongodb/mapping/mapping.adoc b/src/main/antora/modules/ROOT/pages/mongodb/mapping/mapping.adoc
index 4b0b7fb39..7d6db21b6 100644
--- a/src/main/antora/modules/ROOT/pages/mongodb/mapping/mapping.adoc
+++ b/src/main/antora/modules/ROOT/pages/mongodb/mapping/mapping.adoc
@@ -9,21 +9,26 @@ The `MappingMongoConverter` also lets you map objects to documents without provi
This section describes the features of the `MappingMongoConverter`, including fundamentals, how to use conventions for mapping objects to documents and how to override those conventions with annotation-based mapping metadata.
+include::{commons}@data-commons::page$object-mapping.adoc[leveloffset=+1]
+
[[mapping-conventions]]
== Convention-based Mapping
-`MappingMongoConverter` has a few conventions for mapping objects to documents when no additional mapping metadata is provided. The conventions are:
+`MappingMongoConverter` has a few conventions for mapping objects to documents when no additional mapping metadata is provided.
+The conventions are:
-* The short Java class name is mapped to the collection name in the following manner. The class `com.bigbank.SavingsAccount` maps to the `savingsAccount` collection name.
+* The short Java class name is mapped to the collection name in the following manner.
+The class `com.bigbank.SavingsAccount` maps to the `savingsAccount` collection name.
* All nested objects are stored as nested objects in the document and *not* as DBRefs.
* The converter uses any Spring Converters registered with it to override the default mapping of object properties to document fields and values.
-* The fields of an object are used to convert to and from fields in the document. Public `JavaBean` properties are not used.
-* If you have a single non-zero-argument constructor whose constructor argument names match top-level field names of document, that constructor is used. Otherwise, the zero-argument constructor is used. If there is more than one non-zero-argument constructor, an exception will be thrown.
+* The fields of an object are used to convert to and from fields in the document.
+Public `JavaBean` properties are not used.
+* If you have a single non-zero-argument constructor whose constructor argument names match top-level field names of document, that constructor is used.Otherwise, the zero-argument constructor is used.If there is more than one non-zero-argument constructor, an exception will be thrown.
[[mapping.conventions.id-field]]
=== How the `_id` field is handled in the mapping layer.
-MongoDB requires that you have an `_id` field for all documents. If you don't provide one the driver will assign a ObjectId with a generated value. The "_id" field can be of any type the, other than arrays, so long as it is unique. The driver naturally supports all primitive types and Dates. When using the `MappingMongoConverter` there are certain rules that govern how properties from the Java class is mapped to this `_id` field.
+MongoDB requires that you have an `_id` field for all documents.If you don't provide one the driver will assign a ObjectId with a generated value.The "_id" field can be of any type the, other than arrays, so long as it is unique.The driver naturally supports all primitive types and Dates.When using the `MappingMongoConverter` there are certain rules that govern how properties from the Java class is mapped to this `_id` field.
The following outlines what field will be mapped to the `_id` document field:
@@ -569,6 +574,7 @@ Additional examples for using the `@PersistenceConstructor` annotation can be fo
[[mapping-usage-events]]
=== Mapping Framework Events
-Events are fired throughout the lifecycle of the mapping process. This is described in the xref:mongodb/mapping/lifecycle-events.adoc[Lifecycle Events] section.
+Events are fired throughout the lifecycle of the mapping process.
+This is described in the xref:mongodb/lifecycle-events.adoc[Lifecycle Events] section.
Declaring these beans in your Spring ApplicationContext causes them to be invoked whenever the event is dispatched.
diff --git a/src/main/antora/modules/ROOT/pages/mongodb/mongo-encryption.adoc b/src/main/antora/modules/ROOT/pages/mongodb/mongo-encryption.adoc
index 6487f39ba..c24ec5ca7 100644
--- a/src/main/antora/modules/ROOT/pages/mongodb/mongo-encryption.adoc
+++ b/src/main/antora/modules/ROOT/pages/mongodb/mongo-encryption.adoc
@@ -15,9 +15,9 @@ Specific data types require deterministic encryption to preserve equality compar
== Automatic Encryption
MongoDB supports https://www.mongodb.com/docs/manual/core/csfle/[Client-Side Field Level Encryption] out of the box using the MongoDB driver with its Automatic Encryption feature.
-Automatic Encryption requires a xref:mongodb/template-collection-schema.adoc[JSON Schema] that allows to perform encrypted read and write operations without the need to provide an explicit en-/decryption step.
+Automatic Encryption requires a xref:mongodb/mapping/mapping-schema.adoc[JSON Schema] that allows to perform encrypted read and write operations without the need to provide an explicit en-/decryption step.
-Please refer to the xref:mongodb/template-collection-schema.adoc#mongo.jsonSchema.encrypted-fields[JSON Schema] section for more information on defining a JSON Schema that holds encryption information.
+Please refer to the xref:mongodb/mapping/mapping-schema.adoc#mongo.jsonSchema.encrypted-fields[JSON Schema] section for more information on defining a JSON Schema that holds encryption information.
To make use of a the `MongoJsonSchema` it needs to be combined with `AutoEncryptionSettings` which can be done eg. via a `MongoClientSettingsBuilderCustomizer`.
@@ -50,7 +50,7 @@ MongoClientSettingsBuilderCustomizer customizer(MappingContext mappingContext) {
== Explicit Encryption
Explicit encryption uses the MongoDB driver's encryption library (`org.mongodb:mongodb-crypt`) to perform encryption and decryption tasks.
-The `@ExplicitEncrypted` annotation is a combination of the `@Encrypted` annotation used for xref:mongodb/template-collection-schema.adoc#mongo.jsonSchema.encrypted-fields[JSON Schema creation] and a xref:mongodb/mapping/property-converters.adoc[Property Converter].
+The `@ExplicitEncrypted` annotation is a combination of the `@Encrypted` annotation used for xref:mongodb/mapping/mapping-schema.adoc#mongo.jsonSchema.encrypted-fields[JSON Schema creation] and a xref:mongodb/mapping/property-converters.adoc[Property Converter].
In other words, `@ExplicitEncrypted` uses existing building blocks to combine them for simplified explicit encryption support.
[NOTE]
diff --git a/src/main/antora/modules/ROOT/pages/mongodb/repositories/query-methods.adoc b/src/main/antora/modules/ROOT/pages/mongodb/repositories/query-methods.adoc
new file mode 100644
index 000000000..961e87c8d
--- /dev/null
+++ b/src/main/antora/modules/ROOT/pages/mongodb/repositories/query-methods.adoc
@@ -0,0 +1,875 @@
+[[mongodb.repositories.queries]]
+= MongoDB-specific Query Methods
+
+Most of the data access operations you usually trigger on a repository result in a query being executed against the MongoDB databases.
+Defining such a query is a matter of declaring a method on the repository interface, as the following example shows:
+
+.PersonRepository with query methods
+[tabs]
+======
+Imperative::
++
+[source,java,indent=0,subs="verbatim,quotes",role="primary"]
+----
+public interface PersonRepository extends PagingAndSortingRepository {
+
+ List findByLastname(String lastname); <1>
+
+ Page findByFirstname(String firstname, Pageable pageable); <2>
+
+ Person findByShippingAddresses(Address address); <3>
+
+ Person findFirstByLastname(String lastname); <4>
+
+ Stream findAllBy(); <5>
+}
+----
+<1> The `findByLastname` method shows a query for all people with the given last name.
+The query is derived by parsing the method name for constraints that can be concatenated with `And` and `Or`.
+Thus, the method name results in a query expression of `{"lastname" : lastname}`.
+<2> Applies pagination to a query.
+You can equip your method signature with a `Pageable` parameter and let the method return a `Page` instance and Spring Data automatically pages the query accordingly.
+<3> Shows that you can query based on properties that are not primitive types.
+Throws `IncorrectResultSizeDataAccessException` if more than one match is found.
+<4> Uses the `First` keyword to restrict the query to only the first result.
+Unlike <3>, this method does not throw an exception if more than one match is found.
+<5> Uses a Java 8 `Stream` that reads and converts individual elements while iterating the stream.
+
+Reactive::
++
+====
+[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
+----
+public interface ReactivePersonRepository extends ReactiveSortingRepository {
+
+ Flux findByFirstname(String firstname); <1>
+
+ Flux findByFirstname(Publisher firstname); <2>
+
+ Flux findByFirstnameOrderByLastname(String firstname, Pageable pageable); <3>
+
+ Mono findByFirstnameAndLastname(String firstname, String lastname); <4>
+
+ Mono findFirstByLastname(String lastname); <5>
+}
+----
+<1> The method shows a query for all people with the given `lastname`. The query is derived by parsing the method name for constraints that can be concatenated with `And` and `Or`. Thus, the method name results in a query expression of `{"lastname" : lastname}`.
+<2> The method shows a query for all people with the given `firstname` once the `firstname` is emitted by the given `Publisher`.
+<3> Use `Pageable` to pass offset and sorting parameters to the database.
+<4> Find a single entity for the given criteria. It completes with `IncorrectResultSizeDataAccessException` on non-unique results.
+<5> Unless <4>, the first entity is always emitted even if the query yields more result documents.
+
+WARNING: The `Page` return type (as in `Mono`) is not supported by reactive repositories.
+
+It is possible to use `Pageable` in derived finder methods, to pass on `sort`, `limit` and `offset` parameters to the query to reduce load and network traffic.
+The returned `Flux` will only emit data within the declared range.
+
+[source,java]
+----
+Pageable page = PageRequest.of(1, 10, Sort.by("lastname"));
+Flux persons = repository.findByFirstnameOrderByLastname("luke", page);
+----
+====
+======
+
+NOTE: We do not support referring to parameters that are mapped as `DBRef` in the domain class.
+
+The following table shows the keywords that are supported for query methods:
+
+[cols="1,2,3",options="header"]
+.Supported keywords for query methods
+|===
+| Keyword
+| Sample
+| Logical result
+
+| `After`
+| `findByBirthdateAfter(Date date)`
+| `{"birthdate" : {"$gt" : date}}`
+
+| `GreaterThan`
+| `findByAgeGreaterThan(int age)`
+| `{"age" : {"$gt" : age}}`
+
+| `GreaterThanEqual`
+| `findByAgeGreaterThanEqual(int age)`
+| `{"age" : {"$gte" : age}}`
+
+| `Before`
+| `findByBirthdateBefore(Date date)`
+| `{"birthdate" : {"$lt" : date}}`
+
+| `LessThan`
+| `findByAgeLessThan(int age)`
+| `{"age" : {"$lt" : age}}`
+
+| `LessThanEqual`
+| `findByAgeLessThanEqual(int age)`
+| `{"age" : {"$lte" : age}}`
+
+| `Between`
+| `findByAgeBetween(int from, int to)` +
+`findByAgeBetween(Range range)`
+| `{"age" : {"$gt" : from, "$lt" : to}}` +
+lower / upper bounds (`$gt` / `$gte` & `$lt` / `$lte`) according to `Range`
+
+| `In`
+| `findByAgeIn(Collection ages)`
+| `{"age" : {"$in" : [ages...]}}`
+
+| `NotIn`
+| `findByAgeNotIn(Collection ages)`
+| `{"age" : {"$nin" : [ages...]}}`
+
+| `IsNotNull`, `NotNull`
+| `findByFirstnameNotNull()`
+| `{"firstname" : {"$ne" : null}}`
+
+| `IsNull`, `Null`
+| `findByFirstnameNull()`
+| `{"firstname" : null}`
+
+| `Like`, `StartingWith`, `EndingWith`
+| `findByFirstnameLike(String name)`
+| `{"firstname" : name} (name as regex)`
+
+| `NotLike`, `IsNotLike`
+| `findByFirstnameNotLike(String name)`
+| `{"firstname" : { "$not" : name }} (name as regex)`
+
+| `Containing` on String
+| `findByFirstnameContaining(String name)`
+| `{"firstname" : name} (name as regex)`
+
+| `NotContaining` on String
+| `findByFirstnameNotContaining(String name)`
+| `{"firstname" : { "$not" : name}} (name as regex)`
+
+| `Containing` on Collection
+| `findByAddressesContaining(Address address)`
+| `{"addresses" : { "$in" : address}}`
+
+| `NotContaining` on Collection
+| `findByAddressesNotContaining(Address address)`
+| `{"addresses" : { "$not" : { "$in" : address}}}`
+
+| `Regex`
+| `findByFirstnameRegex(String firstname)`
+| `{"firstname" : {"$regex" : firstname }}`
+
+| `(No keyword)`
+| `findByFirstname(String name)`
+| `{"firstname" : name}`
+
+| `Not`
+| `findByFirstnameNot(String name)`
+| `{"firstname" : {"$ne" : name}}`
+
+| `Near`
+| `findByLocationNear(Point point)`
+| `{"location" : {"$near" : [x,y]}}`
+
+| `Near`
+| `findByLocationNear(Point point, Distance max)`
+| `{"location" : {"$near" : [x,y], "$maxDistance" : max}}`
+
+| `Near`
+| `findByLocationNear(Point point, Distance min, Distance max)`
+| `{"location" : {"$near" : [x,y], "$minDistance" : min, "$maxDistance" : max}}`
+
+| `Within`
+| `findByLocationWithin(Circle circle)`
+| `{"location" : {"$geoWithin" : {"$center" : [ [x, y], distance]}}}`
+
+| `Within`
+| `findByLocationWithin(Box box)`
+| `{"location" : {"$geoWithin" : {"$box" : [ [x1, y1], x2, y2]}}}`
+
+| `IsTrue`, `True`
+| `findByActiveIsTrue()`
+| `{"active" : true}`
+
+| `IsFalse`, `False`
+| `findByActiveIsFalse()`
+| `{"active" : false}`
+
+| `Exists`
+| `findByLocationExists(boolean exists)`
+| `{"location" : {"$exists" : exists }}`
+
+| `IgnoreCase`
+| `findByUsernameIgnoreCase(String username)`
+| `{"username" : {"$regex" : "^username$", "$options" : "i" }}`
+|===
+
+NOTE: If the property criterion compares a document, the order of the fields and exact equality in the document matters.
+
+[[mongodb.repositories.queries.geo-spatial]]
+== Geo-spatial Queries
+
+As you saw in the preceding table of keywords, a few keywords trigger geo-spatial operations within a MongoDB query.
+The `Near` keyword allows some further modification, as the next few examples show.
+
+The following example shows how to define a `near` query that finds all persons with a given distance of a given point:
+
+.Advanced `Near` queries
+[tabs]
+======
+Imperative::
++
+[source,java,indent=0,subs="verbatim,quotes",role="primary"]
+----
+public interface PersonRepository extends MongoRepository {
+
+ // { 'location' : { '$near' : [point.x, point.y], '$maxDistance' : distance}}
+ List findByLocationNear(Point location, Distance distance);
+}
+----
+
+Reactive::
++
+[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
+----
+interface PersonRepository extends ReactiveMongoRepository {
+
+ // { 'location' : { '$near' : [point.x, point.y], '$maxDistance' : distance}}
+ Flux findByLocationNear(Point location, Distance distance);
+}
+----
+======
+
+Adding a `Distance` parameter to the query method allows restricting results to those within the given distance.
+If the `Distance` was set up containing a `Metric`, we transparently use `$nearSphere` instead of `$code`, as the following example shows:
+
+.Using `Distance` with `Metrics`
+====
+[source,java]
+----
+Point point = new Point(43.7, 48.8);
+Distance distance = new Distance(200, Metrics.KILOMETERS);
+… = repository.findByLocationNear(point, distance);
+// {'location' : {'$nearSphere' : [43.7, 48.8], '$maxDistance' : 0.03135711885774796}}
+----
+====
+
+NOTE: Reactive Geo-spatial repository queries support the domain type and `GeoResult` results within a reactive wrapper type. `GeoPage` and `GeoResults` are not supported as they contradict the deferred result approach with pre-calculating the average distance. However, you can still pass in a `Pageable` argument to page results yourself.
+
+Using a `Distance` with a `Metric` causes a `$nearSphere` (instead of a plain `$near`) clause to be added.
+Beyond that, the actual distance gets calculated according to the `Metrics` used.
+
+(Note that `Metric` does not refer to metric units of measure.
+It could be miles rather than kilometers.
+Rather, `metric` refers to the concept of a system of measurement, regardless of which system you use.)
+
+NOTE: Using `@GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)` on the target property forces usage of the `$nearSphere` operator.
+
+[[geo-near-queries]]
+=== Geo-near Queries
+
+Spring Data MongoDb supports geo-near queries, as the following example shows:
+
+[tabs]
+======
+Imperative::
++
+[source,java,indent=0,subs="verbatim,quotes",role="primary"]
+----
+public interface PersonRepository extends MongoRepository {
+
+ // {'geoNear' : 'location', 'near' : [x, y] }
+ GeoResults findByLocationNear(Point location);
+
+ // No metric: {'geoNear' : 'person', 'near' : [x, y], maxDistance : distance }
+ // Metric: {'geoNear' : 'person', 'near' : [x, y], 'maxDistance' : distance,
+ // 'distanceMultiplier' : metric.multiplier, 'spherical' : true }
+ GeoResults findByLocationNear(Point location, Distance distance);
+
+ // Metric: {'geoNear' : 'person', 'near' : [x, y], 'minDistance' : min,
+ // 'maxDistance' : max, 'distanceMultiplier' : metric.multiplier,
+ // 'spherical' : true }
+ GeoResults findByLocationNear(Point location, Distance min, Distance max);
+
+ // {'geoNear' : 'location', 'near' : [x, y] }
+ GeoResults findByLocationNear(Point location);
+}
+----
+
+Reactive::
++
+[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
+----
+interface PersonRepository extends ReactiveMongoRepository {
+
+ // {'geoNear' : 'location', 'near' : [x, y] }
+ Flux> findByLocationNear(Point location);
+
+ // No metric: {'geoNear' : 'person', 'near' : [x, y], maxDistance : distance }
+ // Metric: {'geoNear' : 'person', 'near' : [x, y], 'maxDistance' : distance,
+ // 'distanceMultiplier' : metric.multiplier, 'spherical' : true }
+ Flux> findByLocationNear(Point location, Distance distance);
+
+ // Metric: {'geoNear' : 'person', 'near' : [x, y], 'minDistance' : min,
+ // 'maxDistance' : max, 'distanceMultiplier' : metric.multiplier,
+ // 'spherical' : true }
+ Flux> findByLocationNear(Point location, Distance min, Distance max);
+
+ // {'geoNear' : 'location', 'near' : [x, y] }
+ Flux> findByLocationNear(Point location);
+}
+----
+======
+
+[[mongodb.repositories.queries.json-based]]
+== JSON-based Query Methods and Field Restriction
+
+By adding the `org.springframework.data.mongodb.repository.Query` annotation to your repository query methods, you can specify a MongoDB JSON query string to use instead of having the query be derived from the method name, as the following example shows:
+
+[tabs]
+======
+Imperative::
++
+[source,java,indent=0,subs="verbatim,quotes",role="primary"]
+----
+public interface PersonRepository extends MongoRepository {
+
+ @Query("{ 'firstname' : ?0 }")
+ List findByThePersonsFirstname(String firstname);
+
+}
+----
+
+Reactive::
++
+[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
+----
+public interface PersonRepository extends ReactiveMongoRepository {
+
+ @Query("{ 'firstname' : ?0 }")
+ Flux findByThePersonsFirstname(String firstname);
+
+}
+----
+======
+
+The `?0` placeholder lets you substitute the value from the method arguments into the JSON query string.
+
+NOTE: `String` parameter values are escaped during the binding process, which means that it is not possible to add MongoDB specific operators through the argument.
+
+You can also use the filter property to restrict the set of properties that is mapped into the Java object, as the following example shows:
+
+[tabs]
+======
+Imperative::
++
+[source,java,indent=0,subs="verbatim,quotes",role="primary"]
+----
+public interface PersonRepository extends MongoRepository {
+
+ @Query(value="{ 'firstname' : ?0 }", fields="{ 'firstname' : 1, 'lastname' : 1}")
+ List findByThePersonsFirstname(String firstname);
+
+}
+----
+
+Reactive::
++
+[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
+----
+public interface PersonRepository extends ReactiveMongoRepository {
+
+ @Query(value="{ 'firstname' : ?0 }", fields="{ 'firstname' : 1, 'lastname' : 1}")
+ Flux findByThePersonsFirstname(String firstname);
+
+}
+----
+======
+
+The query in the preceding example returns only the `firstname`, `lastname` and `Id` properties of the `Person` objects.
+The `age` property, a `java.lang.Integer`, is not set and its value is therefore null.
+
+[[mongodb.repositories.queries.sort]]
+== Sorting Results
+
+MongoDB repositories allow various approaches to define sorting order.
+Let's take a look at the following example:
+
+.Sorting Query Results
+[tabs]
+======
+Imperative::
++
+[source,java,indent=0,subs="verbatim,quotes",role="primary"]
+----
+public interface PersonRepository extends MongoRepository {
+
+ List findByFirstnameSortByAgeDesc(String firstname); <1>
+
+ List findByFirstname(String firstname, Sort sort); <2>
+
+ @Query(sort = "{ age : -1 }")
+ List findByFirstname(String firstname); <3>
+
+ @Query(sort = "{ age : -1 }")
+ List findByLastname(String lastname, Sort sort); <4>
+}
+----
+<1> Static sorting derived from method name. `SortByAgeDesc` results in `{ age : -1 }` for the sort parameter.
+<2> Dynamic sorting using a method argument.
+`Sort.by(DESC, "age")` creates `{ age : -1 }` for the sort parameter.
+<3> Static sorting via `Query` annotation.
+Sort parameter applied as stated in the `sort` attribute.
+<4> Default sorting via `Query` annotation combined with dynamic one via a method argument. `Sort.unsorted()`
+results in `{ age : -1 }`.
+Using `Sort.by(ASC, "age")` overrides the defaults and creates `{ age : 1 }`.
+`Sort.by
+(ASC, "firstname")` alters the default and results in `{ age : -1, firstname : 1 }`.
+
+Reactive::
++
+[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
+----
+public interface PersonRepository extends ReactiveMongoRepository {
+
+ Flux findByFirstnameSortByAgeDesc(String firstname);
+
+ Flux findByFirstname(String firstname, Sort sort);
+
+ @Query(sort = "{ age : -1 }")
+ Flux findByFirstname(String firstname);
+
+ @Query(sort = "{ age : -1 }")
+ Flux findByLastname(String lastname, Sort sort);
+}
+----
+======
+
+[[mongodb.repositories.queries.json-spel]]
+== JSON-based Queries with SpEL Expressions
+
+Query strings and field definitions can be used together with SpEL expressions to create dynamic queries at runtime.
+SpEL expressions can provide predicate values and can be used to extend predicates with subdocuments.
+
+Expressions expose method arguments through an array that contains all the arguments.
+The following query uses `[0]`
+to declare the predicate value for `lastname` (which is equivalent to the `?0` parameter binding):
+
+[tabs]
+======
+Imperative::
++
+[source,java,indent=0,subs="verbatim,quotes",role="primary"]
+----
+public interface PersonRepository extends MongoRepository {
+
+ @Query("{'lastname': ?#{[0]} }")
+ List findByQueryWithExpression(String param0);
+}
+----
+
+Reactive::
++
+[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
+----
+public interface PersonRepository extends ReactiveMongoRepository {
+
+ @Query("{'lastname': ?#{[0]} }")
+ Flux findByQueryWithExpression(String param0);
+}
+----
+======
+
+Expressions can be used to invoke functions, evaluate conditionals, and construct values.
+SpEL expressions used in conjunction with JSON reveal a side-effect, because Map-like declarations inside of SpEL read like JSON, as the following example shows:
+
+[tabs]
+======
+Imperative::
++
+[source,java,indent=0,subs="verbatim,quotes",role="primary"]
+----
+public interface PersonRepository extends MongoRepository {
+
+ @Query("{'id': ?#{ [0] ? {$exists :true} : [1] }}")
+ List findByQueryWithExpressionAndNestedObject(boolean param0, String param1);
+}
+----
+
+Reactive::
++
+[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
+----
+public interface PersonRepository extends ReactiveMongoRepository {
+
+ @Query("{'id': ?#{ [0] ? {$exists :true} : [1] }}")
+ Flux findByQueryWithExpressionAndNestedObject(boolean param0, String param1);
+}
+----
+======
+
+WARNING: SpEL in query strings can be a powerful way to enhance queries.
+However, they can also accept a broad range of unwanted arguments.
+Make sure to sanitize strings before passing them to the query to avoid creation of vulnerabilities or unwanted changes to your query.
+
+Expression support is extensible through the Query SPI: `EvaluationContextExtension` & `ReactiveEvaluationContextExtension`
+The Query SPI can contribute properties and functions and can customize the root object.
+Extensions are retrieved from the application context at the time of SpEL evaluation when the query is built.
+The following example shows how to use an evaluation context extension:
+
+[tabs]
+======
+Imperative::
++
+[source,java,indent=0,subs="verbatim,quotes",role="primary"]
+----
+public class SampleEvaluationContextExtension extends EvaluationContextExtensionSupport {
+
+ @Override
+ public String getExtensionId() {
+ return "security";
+ }
+
+ @Override
+ public Map getProperties() {
+ return Collections.singletonMap("principal", SecurityContextHolder.getCurrent().getPrincipal());
+ }
+}
+----
+
+Reactive::
++
+[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
+----
+public class SampleEvaluationContextExtension implements ReactiveEvaluationContextExtension {
+
+ @Override
+ public String getExtensionId() {
+ return "security";
+ }
+
+ @Override
+ public Mono extends EvaluationContextExtension> getExtension() {
+ return Mono.just(new EvaluationContextExtensionSupport() { ... });
+ }
+}
+----
+======
+
+NOTE: Bootstrapping `MongoRepositoryFactory` yourself is not application context-aware and requires further configuration to pick up Query SPI extensions.
+
+NOTE: Reactive query methods can make use of `org.springframework.data.spel.spi.ReactiveEvaluationContextExtension`.
+
+[[mongodb.repositories.queries.update]]
+== Update Methods
+
+You can also use the keywords in the preceding table to create queries that identify matching documents for running updates on them.
+The actual update action is defined by the `@Update` annotation on the method itself, as the following listing shows.
+Note that the naming schema for derived queries starts with `find`.
+Using `update` (as in `updateAllByLastname(...)`) is allowed only in combination with `@Query`.
+
+The update is applied to *all* matching documents and it is *not* possible to limit the scope by passing in a `Page` or by using any of the <>.
+The return type can be either `void` or a _numeric_ type, such as `long`, to hold the number of modified documents.
+
+.Update Methods
+====
+[source,java]
+----
+public interface PersonRepository extends CrudRepository {
+
+ @Update("{ '$inc' : { 'visits' : 1 } }")
+ long findAndIncrementVisitsByLastname(String lastname); <1>
+
+ @Update("{ '$inc' : { 'visits' : ?1 } }")
+ void findAndIncrementVisitsByLastname(String lastname, int increment); <2>
+
+ @Update("{ '$inc' : { 'visits' : ?#{[1]} } }")
+ long findAndIncrementVisitsUsingSpELByLastname(String lastname, int increment); <3>
+
+ @Update(pipeline = {"{ '$set' : { 'visits' : { '$add' : [ '$visits', ?1 ] } } }"})
+ void findAndIncrementVisitsViaPipelineByLastname(String lastname, int increment); <4>
+
+ @Update("{ '$push' : { 'shippingAddresses' : ?1 } }")
+ long findAndPushShippingAddressByEmail(String email, Address address); <5>
+
+ @Query("{ 'lastname' : ?0 }")
+ @Update("{ '$inc' : { 'visits' : ?1 } }")
+ void updateAllByLastname(String lastname, int increment); <6>
+}
+----
+
+<1> The filter query for the update is derived from the method name.
+The update is "`as is`" and does not bind any parameters.
+<2> The actual increment value is defined by the `increment` method argument that is bound to the `?1` placeholder.
+<3> Use the Spring Expression Language (SpEL) for parameter binding.
+<4> Use the `pipeline` attribute to issue xref:mongodb/template-crud-operations.adoc#mongo-template.aggregation-update[aggregation pipeline updates].
+<5> The update may contain complex objects.
+<6> Combine a xref:mongodb/repositories/repositories.adoc#mongodb.repositories.queries.json-based[string based query] with an update.
+====
+
+WARNING: Repository updates do not emit persistence nor mapping lifecycle events.
+
+[[mongodb.repositories.queries.delete]]
+== Delete Methods
+
+The keywords in the preceding table can be used in conjunction with `delete…By` or `remove…By` to create queries that delete matching documents.
+
+.`Delete…By` Query
+[tabs]
+======
+Imperative::
++
+[source,java,indent=0,subs="verbatim,quotes",role="primary"]
+----
+public interface PersonRepository extends MongoRepository {
+
+ List deleteByLastname(String lastname); <1>
+
+ Long deletePersonByLastname(String lastname); <2>
+
+ @Nullable
+ Person deleteSingleByLastname(String lastname); <3>
+
+ Optional deleteByBirthdate(Date birthdate); <4>
+}
+----
+<1> Using a return type of `List` retrieves and returns all matching documents before actually deleting them.
+<2> A numeric return type directly removes the matching documents, returning the total number of documents removed.
+<3> A single domain type result retrieves and removes the first matching document.
+<4> Same as in 3 but wrapped in an `Optional` type.
+
+Reactive::
++
+[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
+----
+public interface PersonRepository extends ReactiveMongoRepository {
+
+ Flux deleteByLastname(String lastname); <1>
+
+ Mono deletePersonByLastname(String lastname); <2>
+
+ Mono deleteSingleByLastname(String lastname); <3>
+}
+----
+<1> Using a return type of `Flux` retrieves and returns all matching documents before actually deleting them.
+<2> A numeric return type directly removes the matching documents, returning the total number of documents removed.
+<3> A single domain type result retrieves and removes the first matching document.
+======
+
+[[mongodb.repositories.queries.aggregation]]
+== Aggregation Methods
+
+The repository layer offers means to interact with xref:mongodb/aggregation-framework.adoc[the aggregation framework] via annotated repository query methods.
+Similar to the xref:mongodb/repositories/repositories.adoc#mongodb.repositories.queries.json-based[JSON based queries], you can define a pipeline using the `org.springframework.data.mongodb.repository.Aggregation` annotation.
+The definition may contain simple placeholders like `?0` as well as link:{springDocsUrl}/core.html#expressions[SpEL expressions] `?#{ … }`.
+
+.Aggregating Repository Method
+====
+[source,java]
+----
+public interface PersonRepository extends CrudRepository {
+
+ @Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
+ List groupByLastnameAndFirstnames(); <1>
+
+ @Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
+ List groupByLastnameAndFirstnames(Sort sort); <2>
+
+ @Aggregation("{ $group: { _id : $lastname, names : { $addToSet : ?0 } } }")
+ List groupByLastnameAnd(String property); <3>
+
+ @Aggregation("{ $group: { _id : $lastname, names : { $addToSet : ?0 } } }")
+ Slice groupByLastnameAnd(String property, Pageable page); <4>
+
+ @Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
+ Stream groupByLastnameAndFirstnamesAsStream(); <5>
+
+ @Aggregation("{ $group : { _id : null, total : { $sum : $age } } }")
+ SumValue sumAgeUsingValueWrapper(); <6>
+
+ @Aggregation("{ $group : { _id : null, total : { $sum : $age } } }")
+ Long sumAge(); <7>
+
+ @Aggregation("{ $group : { _id : null, total : { $sum : $age } } }")
+ AggregationResults sumAgeRaw(); <8>
+
+ @Aggregation("{ '$project': { '_id' : '$lastname' } }")
+ List findAllLastnames(); <9>
+
+ @Aggregation(pipeline = {
+ "{ $group : { _id : '$author', books: { $push: '$title' } } }",
+ "{ $out : 'authors' }"
+ })
+ void groupAndOutSkippingOutput(); <10>
+}
+----
+[source,java]
+----
+public class PersonAggregate {
+
+ private @Id String lastname; <2>
+ private List names;
+
+ public PersonAggregate(String lastname, List names) {
+ // ...
+ }
+
+ // Getter / Setter omitted
+}
+
+public class SumValue {
+
+ private final Long total; <6> <8>
+
+ public SumValue(Long total) {
+ // ...
+ }
+
+ // Getter omitted
+}
+----
+<1> Aggregation pipeline to group first names by `lastname` in the `Person` collection returning these as `PersonAggregate`.
+<2> If `Sort` argument is present, `$sort` is appended after the declared pipeline stages so that it only affects the order of the final results after having passed all other aggregation stages.
+Therefore, the `Sort` properties are mapped against the methods return type `PersonAggregate` which turns `Sort.by("lastname")` into `{ $sort : { '_id', 1 } }` because `PersonAggregate.lastname` is annotated with `@Id`.
+<3> Replaces `?0` with the given value for `property` for a dynamic aggregation pipeline.
+<4> `$skip`, `$limit` and `$sort` can be passed on via a `Pageable` argument. Same as in <2>, the operators are appended to the pipeline definition. Methods accepting `Pageable` can return `Slice` for easier pagination.
+<5> Aggregation methods can return `Stream` to consume results directly from an underlying cursor. Make sure to close the stream after consuming it to release the server-side cursor by either calling `close()` or through `try-with-resources`.
+<6> Map the result of an aggregation returning a single `Document` to an instance of a desired `SumValue` target type.
+<7> Aggregations resulting in single document holding just an accumulation result like e.g. `$sum` can be extracted directly from the result `Document`.
+To gain more control, you might consider `AggregationResult` as method return type as shown in <7>.
+<8> Obtain the raw `AggregationResults` mapped to the generic target wrapper type `SumValue` or `org.bson.Document`.
+<9> Like in <6>, a single value can be directly obtained from multiple result ``Document``s.
+<10> Skips the output of the `$out` stage when return type is `void`.
+====
+
+In some scenarios, aggregations might require additional options, such as a maximum run time, additional log comments, or the permission to temporarily write data to disk.
+Use the `@Meta` annotation to set those options via `maxExecutionTimeMs`, `comment` or `allowDiskUse`.
+
+[source,java]
+----
+interface PersonRepository extends CrudRepository {
+
+ @Meta(allowDiskUse = true)
+ @Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
+ List groupByLastnameAndFirstnames();
+}
+----
+
+Or use `@Meta` to create your own annotation as shown in the sample below.
+
+[source,java]
+----
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD })
+@Meta(allowDiskUse = true)
+@interface AllowDiskUse { }
+
+interface PersonRepository extends CrudRepository {
+
+ @AllowDiskUse
+ @Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
+ List groupByLastnameAndFirstnames();
+}
+----
+
+[NOTE]
+====
+Simple-type single-result inspects the returned `Document` and checks for the following:
+
+. Only one entry in the document, return it.
+. Two entries, one is the `_id` value. Return the other.
+. Return for the first value assignable to the return type.
+. Throw an exception if none of the above is applicable.
+====
+
+WARNING: The `Page` return type is not supported for repository methods using `@Aggregation`. However, you can use a
+`Pageable` argument to add `$skip`, `$limit` and `$sort` to the pipeline and let the method return `Slice`.
+
+[[mongodb.repositories.index-hint]]
+== Index Hints
+
+The `@Hint` annotation allows to override MongoDB's default index selection and forces the database to use the specified index instead.
+
+.Example of index hints
+====
+[source,java]
+----
+@Hint("lastname-idx") <1>
+List findByLastname(String lastname);
+
+@Query(value = "{ 'firstname' : ?0 }", hint = "firstname-idx") <2>
+List findByFirstname(String firstname);
+----
+<1> Use the index with name `lastname-idx`.
+<2> The `@Query` annotation defines the `hint` alias which is equivalent to adding the `@Hint` annotation.
+====
+
+For more information about index creation please refer to the xref:mongodb/template-collection-management.adoc[Collection Management] section.
+
+[[mongo.repositories.collation]]
+== Repository Collation Support
+
+Next to the xref:mongodb/collation.adoc[general Collation Support] repositories allow to define the collation for various operations.
+
+====
+[source,java]
+----
+public interface PersonRepository extends MongoRepository {
+
+ @Query(collation = "en_US") <1>
+ List findByFirstname(String firstname);
+
+ @Query(collation = "{ 'locale' : 'en_US' }") <2>
+ List findPersonByFirstname(String firstname);
+
+ @Query(collation = "?1") <3>
+ List findByFirstname(String firstname, Object collation);
+
+ @Query(collation = "{ 'locale' : '?1' }") <4>
+ List findByFirstname(String firstname, String collation);
+
+ List findByFirstname(String firstname, Collation collation); <5>
+
+ @Query(collation = "{ 'locale' : 'en_US' }")
+ List findByFirstname(String firstname, @Nullable Collation collation); <6>
+}
+----
+<1> Static collation definition resulting in `{ 'locale' : 'en_US' }`.
+<2> Static collation definition resulting in `{ 'locale' : 'en_US' }`.
+<3> Dynamic collation depending on 2nd method argument. Allowed types include `String` (eg. 'en_US'), `Locacle` (eg. Locacle.US)
+and `Document` (eg. new Document("locale", "en_US"))
+<4> Dynamic collation depending on 2nd method argument.
+<5> Apply the `Collation` method parameter to the query.
+<6> The `Collation` method parameter overrides the default `collation` from `@Query` if not null.
+
+NOTE: In case you enabled the automatic index creation for repository finder methods a potential static collation definition,
+as shown in (1) and (2), will be included when creating the index.
+
+TIP: The most specifc `Collation` outrules potentially defined others. Which means Method argument over query method annotation over domain type annotation.
+====
+
+To streamline usage of collation attributes throughout the codebase it is also possible to use the `@Collation` annotation, which serves as a meta annotation for the ones mentioned above.
+The same rules and locations apply, plus, direct usage of `@Collation` supersedes any collation values defined on `@Query` and other annotations.
+Which means, if a collation is declared via `@Query` and additionally via `@Collation`, then the one from `@Collation` is picked.
+
+.Using `@Collation`
+====
+[source,java]
+----
+@Collation("en_US") <1>
+class Game {
+ // ...
+}
+
+interface GameRepository extends Repository {
+
+ @Collation("en_GB") <2>
+ List findByTitle(String title);
+
+ @Collation("de_AT") <3>
+ @Query(collation="en_GB")
+ List findByDescriptionContaining(String keyword);
+}
+----
+<1> Instead of `@Document(collation=...)`.
+<2> Instead of `@Query(collation=...)`.
+<3> Favors `@Collation` over meta usage.
+====
diff --git a/src/main/antora/modules/ROOT/pages/mongodb/repositories/repositories-collation.adoc b/src/main/antora/modules/ROOT/pages/mongodb/repositories/repositories-collation.adoc
deleted file mode 100644
index ebf0589eb..000000000
--- a/src/main/antora/modules/ROOT/pages/mongodb/repositories/repositories-collation.adoc
+++ /dev/null
@@ -1,69 +0,0 @@
-[[mongo.repositories.collation]]
-= Repository Collation Support
-
-Next to the xref:mongodb/collation.adoc[general Collation Support] repositories allow to define the collation for various operations.
-
-====
-[source,java]
-----
-public interface PersonRepository extends MongoRepository {
-
- @Query(collation = "en_US") <1>
- List findByFirstname(String firstname);
-
- @Query(collation = "{ 'locale' : 'en_US' }") <2>
- List findPersonByFirstname(String firstname);
-
- @Query(collation = "?1") <3>
- List findByFirstname(String firstname, Object collation);
-
- @Query(collation = "{ 'locale' : '?1' }") <4>
- List findByFirstname(String firstname, String collation);
-
- List findByFirstname(String firstname, Collation collation); <5>
-
- @Query(collation = "{ 'locale' : 'en_US' }")
- List findByFirstname(String firstname, @Nullable Collation collation); <6>
-}
-----
-<1> Static collation definition resulting in `{ 'locale' : 'en_US' }`.
-<2> Static collation definition resulting in `{ 'locale' : 'en_US' }`.
-<3> Dynamic collation depending on 2nd method argument. Allowed types include `String` (eg. 'en_US'), `Locacle` (eg. Locacle.US)
-and `Document` (eg. new Document("locale", "en_US"))
-<4> Dynamic collation depending on 2nd method argument.
-<5> Apply the `Collation` method parameter to the query.
-<6> The `Collation` method parameter overrides the default `collation` from `@Query` if not null.
-
-NOTE: In case you enabled the automatic index creation for repository finder methods a potential static collation definition,
-as shown in (1) and (2), will be included when creating the index.
-
-TIP: The most specifc `Collation` outrules potentially defined others. Which means Method argument over query method annotation over domain type annotation.
-====
-
-To streamline usage of collation attributes throughout the codebase it is also possible to use the `@Collation` annotation, which serves as a meta annotation for the ones mentioned above.
-The same rules and locations apply, plus, direct usage of `@Collation` supersedes any collation values defined on `@Query` and other annotations.
-Which means, if a collation is declared via `@Query` and additionally via `@Collation`, then the one from `@Collation` is picked.
-
-.Using `@Collation`
-====
-[source,java]
-----
-@Collation("en_US") <1>
-class Game {
- // ...
-}
-
-interface GameRepository extends Repository {
-
- @Collation("en_GB") <2>
- List findByTitle(String title);
-
- @Collation("de_AT") <3>
- @Query(collation="en_GB")
- List findByDescriptionContaining(String keyword);
-}
-----
-<1> Instead of `@Document(collation=...)`.
-<2> Instead of `@Query(collation=...)`.
-<3> Favors `@Collation` over meta usage.
-====
diff --git a/src/main/antora/modules/ROOT/pages/mongodb/repositories/repositories-index-hints.adoc b/src/main/antora/modules/ROOT/pages/mongodb/repositories/repositories-index-hints.adoc
deleted file mode 100644
index 555bbf96e..000000000
--- a/src/main/antora/modules/ROOT/pages/mongodb/repositories/repositories-index-hints.adoc
+++ /dev/null
@@ -1,20 +0,0 @@
-[[mongodb.repositories.index-hint]]
-= Index Hints
-
-The `@Hint` annotation allows to override MongoDB's default index selection and forces the database to use the specified index instead.
-
-.Example of index hints
-====
-[source,java]
-----
-@Hint("lastname-idx") <1>
-List findByLastname(String lastname);
-
-@Query(value = "{ 'firstname' : ?0 }", hint = "firstname-idx") <2>
-List findByFirstname(String firstname);
-----
-<1> Use the index with name `lastname-idx`.
-<2> The `@Query` annotation defines the `hint` alias which is equivalent to adding the `@Hint` annotation.
-====
-
-For more information about index creation please refer to the xref:mongodb/template-collection-management.adoc[Collection Management] section.
diff --git a/src/main/antora/modules/ROOT/pages/mongodb/repositories/repositories.adoc b/src/main/antora/modules/ROOT/pages/mongodb/repositories/repositories.adoc
index 97149d3e6..ae8d68dd6 100644
--- a/src/main/antora/modules/ROOT/pages/mongodb/repositories/repositories.adoc
+++ b/src/main/antora/modules/ROOT/pages/mongodb/repositories/repositories.adoc
@@ -31,7 +31,7 @@ public class Person {
Note that the domain type shown in the preceding example has a property named `id` of type `String`.The default serialization mechanism used in `MongoTemplate` (which backs the repository support) regards properties named `id` as the document ID.
Currently, we support `String`, `ObjectId`, and `BigInteger` as ID types.
-Please see xref:mongodb/template-id-handling.adoc#mongo-template.id-handling[ID mapping] for more information about on how the `id` field is handled in the mapping layer.
+Please see xref:mongodb/template-crud-operations.adoc#mongo-template.id-handling[ID mapping] for more information about on how the `id` field is handled in the mapping layer.
Now that we have a domain object, we can define an interface that uses it, as follows:
@@ -212,568 +212,8 @@ The preceding example creates an application context with Spring's unit test sup
Inside the test method, we use the repository to query the datastore.
We hand the repository a `PageRequest` instance that requests the first page of `Person` objects at a page size of 10.
-[[mongodb.repositories.queries]]
-== Query Methods
-
-Most of the data access operations you usually trigger on a repository result in a query being executed against the MongoDB databases.
-Defining such a query is a matter of declaring a method on the repository interface, as the following example shows:
-
-.PersonRepository with query methods
-[tabs]
-======
-Imperative::
-+
-[source,java,indent=0,subs="verbatim,quotes",role="primary"]
-----
-public interface PersonRepository extends PagingAndSortingRepository {
-
- List findByLastname(String lastname); <1>
-
- Page findByFirstname(String firstname, Pageable pageable); <2>
-
- Person findByShippingAddresses(Address address); <3>
-
- Person findFirstByLastname(String lastname); <4>
-
- Stream findAllBy(); <5>
-}
-----
-<1> The `findByLastname` method shows a query for all people with the given last name.
-The query is derived by parsing the method name for constraints that can be concatenated with `And` and `Or`.
-Thus, the method name results in a query expression of `{"lastname" : lastname}`.
-<2> Applies pagination to a query.
-You can equip your method signature with a `Pageable` parameter and let the method return a `Page` instance and Spring Data automatically pages the query accordingly.
-<3> Shows that you can query based on properties that are not primitive types.
-Throws `IncorrectResultSizeDataAccessException` if more than one match is found.
-<4> Uses the `First` keyword to restrict the query to only the first result.
-Unlike <3>, this method does not throw an exception if more than one match is found.
-<5> Uses a Java 8 `Stream` that reads and converts individual elements while iterating the stream.
-
-Reactive::
-+
-====
-[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
-----
-public interface ReactivePersonRepository extends ReactiveSortingRepository {
-
- Flux findByFirstname(String firstname); <1>
-
- Flux findByFirstname(Publisher firstname); <2>
-
- Flux findByFirstnameOrderByLastname(String firstname, Pageable pageable); <3>
-
- Mono findByFirstnameAndLastname(String firstname, String lastname); <4>
-
- Mono findFirstByLastname(String lastname); <5>
-}
-----
-<1> The method shows a query for all people with the given `lastname`. The query is derived by parsing the method name for constraints that can be concatenated with `And` and `Or`. Thus, the method name results in a query expression of `{"lastname" : lastname}`.
-<2> The method shows a query for all people with the given `firstname` once the `firstname` is emitted by the given `Publisher`.
-<3> Use `Pageable` to pass offset and sorting parameters to the database.
-<4> Find a single entity for the given criteria. It completes with `IncorrectResultSizeDataAccessException` on non-unique results.
-<5> Unless <4>, the first entity is always emitted even if the query yields more result documents.
-
-WARNING: The `Page` return type (as in `Mono`) is not supported by reactive repositories.
-
-It is possible to use `Pageable` in derived finder methods, to pass on `sort`, `limit` and `offset` parameters to the query to reduce load and network traffic.
-The returned `Flux` will only emit data within the declared range.
-
-[source,java]
-----
-Pageable page = PageRequest.of(1, 10, Sort.by("lastname"));
-Flux persons = repository.findByFirstnameOrderByLastname("luke", page);
-----
-====
-======
-
-NOTE: We do not support referring to parameters that are mapped as `DBRef` in the domain class.
-
-The following table shows the keywords that are supported for query methods:
-
-[cols="1,2,3",options="header"]
-.Supported keywords for query methods
-|===
-| Keyword
-| Sample
-| Logical result
-
-| `After`
-| `findByBirthdateAfter(Date date)`
-| `{"birthdate" : {"$gt" : date}}`
-
-| `GreaterThan`
-| `findByAgeGreaterThan(int age)`
-| `{"age" : {"$gt" : age}}`
-
-| `GreaterThanEqual`
-| `findByAgeGreaterThanEqual(int age)`
-| `{"age" : {"$gte" : age}}`
-
-| `Before`
-| `findByBirthdateBefore(Date date)`
-| `{"birthdate" : {"$lt" : date}}`
-
-| `LessThan`
-| `findByAgeLessThan(int age)`
-| `{"age" : {"$lt" : age}}`
-
-| `LessThanEqual`
-| `findByAgeLessThanEqual(int age)`
-| `{"age" : {"$lte" : age}}`
-
-| `Between`
-| `findByAgeBetween(int from, int to)` +
-`findByAgeBetween(Range range)`
-| `{"age" : {"$gt" : from, "$lt" : to}}` +
-lower / upper bounds (`$gt` / `$gte` & `$lt` / `$lte`) according to `Range`
-
-| `In`
-| `findByAgeIn(Collection ages)`
-| `{"age" : {"$in" : [ages...]}}`
-
-| `NotIn`
-| `findByAgeNotIn(Collection ages)`
-| `{"age" : {"$nin" : [ages...]}}`
-
-| `IsNotNull`, `NotNull`
-| `findByFirstnameNotNull()`
-| `{"firstname" : {"$ne" : null}}`
-
-| `IsNull`, `Null`
-| `findByFirstnameNull()`
-| `{"firstname" : null}`
-
-| `Like`, `StartingWith`, `EndingWith`
-| `findByFirstnameLike(String name)`
-| `{"firstname" : name} (name as regex)`
-
-| `NotLike`, `IsNotLike`
-| `findByFirstnameNotLike(String name)`
-| `{"firstname" : { "$not" : name }} (name as regex)`
-
-| `Containing` on String
-| `findByFirstnameContaining(String name)`
-| `{"firstname" : name} (name as regex)`
-
-| `NotContaining` on String
-| `findByFirstnameNotContaining(String name)`
-| `{"firstname" : { "$not" : name}} (name as regex)`
-
-| `Containing` on Collection
-| `findByAddressesContaining(Address address)`
-| `{"addresses" : { "$in" : address}}`
-
-| `NotContaining` on Collection
-| `findByAddressesNotContaining(Address address)`
-| `{"addresses" : { "$not" : { "$in" : address}}}`
-
-| `Regex`
-| `findByFirstnameRegex(String firstname)`
-| `{"firstname" : {"$regex" : firstname }}`
-
-| `(No keyword)`
-| `findByFirstname(String name)`
-| `{"firstname" : name}`
-
-| `Not`
-| `findByFirstnameNot(String name)`
-| `{"firstname" : {"$ne" : name}}`
-
-| `Near`
-| `findByLocationNear(Point point)`
-| `{"location" : {"$near" : [x,y]}}`
-
-| `Near`
-| `findByLocationNear(Point point, Distance max)`
-| `{"location" : {"$near" : [x,y], "$maxDistance" : max}}`
-
-| `Near`
-| `findByLocationNear(Point point, Distance min, Distance max)`
-| `{"location" : {"$near" : [x,y], "$minDistance" : min, "$maxDistance" : max}}`
-
-| `Within`
-| `findByLocationWithin(Circle circle)`
-| `{"location" : {"$geoWithin" : {"$center" : [ [x, y], distance]}}}`
-
-| `Within`
-| `findByLocationWithin(Box box)`
-| `{"location" : {"$geoWithin" : {"$box" : [ [x1, y1], x2, y2]}}}`
-
-| `IsTrue`, `True`
-| `findByActiveIsTrue()`
-| `{"active" : true}`
-
-| `IsFalse`, `False`
-| `findByActiveIsFalse()`
-| `{"active" : false}`
-
-| `Exists`
-| `findByLocationExists(boolean exists)`
-| `{"location" : {"$exists" : exists }}`
-
-| `IgnoreCase`
-| `findByUsernameIgnoreCase(String username)`
-| `{"username" : {"$regex" : "^username$", "$options" : "i" }}`
-|===
-
-NOTE: If the property criterion compares a document, the order of the fields and exact equality in the document matters.
-
-[[mongodb.repositories.queries.geo-spatial]]
-=== Geo-spatial Repository Queries
-
-As you saw in the preceding table of keywords, a few keywords trigger geo-spatial operations within a MongoDB query.
-The `Near` keyword allows some further modification, as the next few examples show.
-
-The following example shows how to define a `near` query that finds all persons with a given distance of a given point:
-
-.Advanced `Near` queries
-[tabs]
-======
-Imperative::
-+
-[source,java,indent=0,subs="verbatim,quotes",role="primary"]
-----
-public interface PersonRepository extends MongoRepository {
-
- // { 'location' : { '$near' : [point.x, point.y], '$maxDistance' : distance}}
- List findByLocationNear(Point location, Distance distance);
-}
-----
-
-Reactive::
-+
-[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
-----
-interface PersonRepository extends ReactiveMongoRepository {
-
- // { 'location' : { '$near' : [point.x, point.y], '$maxDistance' : distance}}
- Flux findByLocationNear(Point location, Distance distance);
-}
-----
-======
-
-Adding a `Distance` parameter to the query method allows restricting results to those within the given distance.
-If the `Distance` was set up containing a `Metric`, we transparently use `$nearSphere` instead of `$code`, as the following example shows:
-
-.Using `Distance` with `Metrics`
-====
-[source,java]
-----
-Point point = new Point(43.7, 48.8);
-Distance distance = new Distance(200, Metrics.KILOMETERS);
-… = repository.findByLocationNear(point, distance);
-// {'location' : {'$nearSphere' : [43.7, 48.8], '$maxDistance' : 0.03135711885774796}}
-----
-====
-
-NOTE: Reactive Geo-spatial repository queries support the domain type and `GeoResult` results within a reactive wrapper type. `GeoPage` and `GeoResults` are not supported as they contradict the deferred result approach with pre-calculating the average distance. However, you can still pass in a `Pageable` argument to page results yourself.
-
-Using a `Distance` with a `Metric` causes a `$nearSphere` (instead of a plain `$near`) clause to be added.
-Beyond that, the actual distance gets calculated according to the `Metrics` used.
-
-(Note that `Metric` does not refer to metric units of measure.
-It could be miles rather than kilometers.
-Rather, `metric` refers to the concept of a system of measurement, regardless of which system you use.)
-
-NOTE: Using `@GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)` on the target property forces usage of the `$nearSphere` operator.
-
-[[geo-near-queries]]
-==== Geo-near Queries
-
-Spring Data MongoDb supports geo-near queries, as the following example shows:
-
-[tabs]
-======
-Imperative::
-+
-[source,java,indent=0,subs="verbatim,quotes",role="primary"]
-----
-public interface PersonRepository extends MongoRepository {
-
- // {'geoNear' : 'location', 'near' : [x, y] }
- GeoResults findByLocationNear(Point location);
-
- // No metric: {'geoNear' : 'person', 'near' : [x, y], maxDistance : distance }
- // Metric: {'geoNear' : 'person', 'near' : [x, y], 'maxDistance' : distance,
- // 'distanceMultiplier' : metric.multiplier, 'spherical' : true }
- GeoResults findByLocationNear(Point location, Distance distance);
-
- // Metric: {'geoNear' : 'person', 'near' : [x, y], 'minDistance' : min,
- // 'maxDistance' : max, 'distanceMultiplier' : metric.multiplier,
- // 'spherical' : true }
- GeoResults findByLocationNear(Point location, Distance min, Distance max);
-
- // {'geoNear' : 'location', 'near' : [x, y] }
- GeoResults findByLocationNear(Point location);
-}
-----
-
-Reactive::
-+
-[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
-----
-interface PersonRepository extends ReactiveMongoRepository {
-
- // {'geoNear' : 'location', 'near' : [x, y] }
- Flux> findByLocationNear(Point location);
-
- // No metric: {'geoNear' : 'person', 'near' : [x, y], maxDistance : distance }
- // Metric: {'geoNear' : 'person', 'near' : [x, y], 'maxDistance' : distance,
- // 'distanceMultiplier' : metric.multiplier, 'spherical' : true }
- Flux> findByLocationNear(Point location, Distance distance);
-
- // Metric: {'geoNear' : 'person', 'near' : [x, y], 'minDistance' : min,
- // 'maxDistance' : max, 'distanceMultiplier' : metric.multiplier,
- // 'spherical' : true }
- Flux> findByLocationNear(Point location, Distance min, Distance max);
-
- // {'geoNear' : 'location', 'near' : [x, y] }
- Flux> findByLocationNear(Point location);
-}
-----
-======
-
-[[mongodb.repositories.queries.json-based]]
-=== MongoDB JSON-based Query Methods and Field Restriction
-
-By adding the `org.springframework.data.mongodb.repository.Query` annotation to your repository query methods, you can specify a MongoDB JSON query string to use instead of having the query be derived from the method name, as the following example shows:
-
-[tabs]
-======
-Imperative::
-+
-[source,java,indent=0,subs="verbatim,quotes",role="primary"]
-----
-public interface PersonRepository extends MongoRepository {
-
- @Query("{ 'firstname' : ?0 }")
- List findByThePersonsFirstname(String firstname);
-
-}
-----
-
-Reactive::
-+
-[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
-----
-public interface PersonRepository extends ReactiveMongoRepository {
-
- @Query("{ 'firstname' : ?0 }")
- Flux findByThePersonsFirstname(String firstname);
-
-}
-----
-======
-
-The `?0` placeholder lets you substitute the value from the method arguments into the JSON query string.
-
-NOTE: `String` parameter values are escaped during the binding process, which means that it is not possible to add MongoDB specific operators through the argument.
-
-You can also use the filter property to restrict the set of properties that is mapped into the Java object, as the following example shows:
-
-[tabs]
-======
-Imperative::
-+
-[source,java,indent=0,subs="verbatim,quotes",role="primary"]
-----
-public interface PersonRepository extends MongoRepository {
-
- @Query(value="{ 'firstname' : ?0 }", fields="{ 'firstname' : 1, 'lastname' : 1}")
- List findByThePersonsFirstname(String firstname);
-
-}
-----
-
-Reactive::
-+
-[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
-----
-public interface PersonRepository extends ReactiveMongoRepository {
-
- @Query(value="{ 'firstname' : ?0 }", fields="{ 'firstname' : 1, 'lastname' : 1}")
- Flux findByThePersonsFirstname(String firstname);
-
-}
-----
-======
-
-The query in the preceding example returns only the `firstname`, `lastname` and `Id` properties of the `Person` objects.
-The `age` property, a `java.lang.Integer`, is not set and its value is therefore null.
-
-[[mongodb.repositories.queries.sort]]
-=== Sorting Query Method results
-
-MongoDB repositories allow various approaches to define sorting order.
-Let's take a look at the following example:
-
-.Sorting Query Results
-[tabs]
-======
-Imperative::
-+
-[source,java,indent=0,subs="verbatim,quotes",role="primary"]
-----
-public interface PersonRepository extends MongoRepository {
-
- List findByFirstnameSortByAgeDesc(String firstname); <1>
-
- List findByFirstname(String firstname, Sort sort); <2>
-
- @Query(sort = "{ age : -1 }")
- List findByFirstname(String firstname); <3>
-
- @Query(sort = "{ age : -1 }")
- List findByLastname(String lastname, Sort sort); <4>
-}
-----
-<1> Static sorting derived from method name. `SortByAgeDesc` results in `{ age : -1 }` for the sort parameter.
-<2> Dynamic sorting using a method argument.
-`Sort.by(DESC, "age")` creates `{ age : -1 }` for the sort parameter.
-<3> Static sorting via `Query` annotation.
-Sort parameter applied as stated in the `sort` attribute.
-<4> Default sorting via `Query` annotation combined with dynamic one via a method argument. `Sort.unsorted()`
-results in `{ age : -1 }`.
-Using `Sort.by(ASC, "age")` overrides the defaults and creates `{ age : 1 }`.
-`Sort.by
-(ASC, "firstname")` alters the default and results in `{ age : -1, firstname : 1 }`.
-
-Reactive::
-+
-[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
-----
-public interface PersonRepository extends ReactiveMongoRepository {
-
- Flux findByFirstnameSortByAgeDesc(String firstname);
-
- Flux findByFirstname(String firstname, Sort sort);
-
- @Query(sort = "{ age : -1 }")
- Flux findByFirstname(String firstname);
-
- @Query(sort = "{ age : -1 }")
- Flux findByLastname(String lastname, Sort sort);
-}
-----
-======
-
-[[mongodb.repositories.queries.json-spel]]
-=== JSON-based Queries with SpEL Expressions
-
-Query strings and field definitions can be used together with SpEL expressions to create dynamic queries at runtime.
-SpEL expressions can provide predicate values and can be used to extend predicates with subdocuments.
-
-Expressions expose method arguments through an array that contains all the arguments.
-The following query uses `[0]`
-to declare the predicate value for `lastname` (which is equivalent to the `?0` parameter binding):
-
-[tabs]
-======
-Imperative::
-+
-[source,java,indent=0,subs="verbatim,quotes",role="primary"]
-----
-public interface PersonRepository extends MongoRepository {
-
- @Query("{'lastname': ?#{[0]} }")
- List findByQueryWithExpression(String param0);
-}
-----
-
-Reactive::
-+
-[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
-----
-public interface PersonRepository extends ReactiveMongoRepository {
-
- @Query("{'lastname': ?#{[0]} }")
- Flux findByQueryWithExpression(String param0);
-}
-----
-======
-
-Expressions can be used to invoke functions, evaluate conditionals, and construct values.
-SpEL expressions used in conjunction with JSON reveal a side-effect, because Map-like declarations inside of SpEL read like JSON, as the following example shows:
-
-[tabs]
-======
-Imperative::
-+
-[source,java,indent=0,subs="verbatim,quotes",role="primary"]
-----
-public interface PersonRepository extends MongoRepository {
-
- @Query("{'id': ?#{ [0] ? {$exists :true} : [1] }}")
- List findByQueryWithExpressionAndNestedObject(boolean param0, String param1);
-}
-----
-
-Reactive::
-+
-[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
-----
-public interface PersonRepository extends ReactiveMongoRepository {
-
- @Query("{'id': ?#{ [0] ? {$exists :true} : [1] }}")
- Flux findByQueryWithExpressionAndNestedObject(boolean param0, String param1);
-}
-----
-======
-
-WARNING: SpEL in query strings can be a powerful way to enhance queries.
-However, they can also accept a broad range of unwanted arguments.
-Make sure to sanitize strings before passing them to the query to avoid creation of vulnerabilities or unwanted changes to your query.
-
-Expression support is extensible through the Query SPI: `EvaluationContextExtension` & `ReactiveEvaluationContextExtension`
-The Query SPI can contribute properties and functions and can customize the root object.
-Extensions are retrieved from the application context at the time of SpEL evaluation when the query is built.
-The following example shows how to use an evaluation context extension:
-
-[tabs]
-======
-Imperative::
-+
-[source,java,indent=0,subs="verbatim,quotes",role="primary"]
-----
-public class SampleEvaluationContextExtension extends EvaluationContextExtensionSupport {
-
- @Override
- public String getExtensionId() {
- return "security";
- }
-
- @Override
- public Map getProperties() {
- return Collections.singletonMap("principal", SecurityContextHolder.getCurrent().getPrincipal());
- }
-}
-----
-
-Reactive::
-+
-[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
-----
-public class SampleEvaluationContextExtension implements ReactiveEvaluationContextExtension {
-
- @Override
- public String getExtensionId() {
- return "security";
- }
-
- @Override
- public Mono extends EvaluationContextExtension> getExtension() {
- return Mono.just(new EvaluationContextExtensionSupport() { ... });
- }
-}
-----
-======
-
-NOTE: Bootstrapping `MongoRepositoryFactory` yourself is not application context-aware and requires further configuration to pick up Query SPI extensions.
-
-NOTE: Reactive query methods can make use of `org.springframework.data.spel.spi.ReactiveEvaluationContextExtension`.
-
[[mongodb.repositories.queries.type-safe]]
-=== Type-safe Query Methods
+== Type-safe Query Methods
MongoDB repository and its reactive counterpart integrates with the http://www.querydsl.com/[Querydsl] project, which provides a way to perform type-safe queries.
@@ -901,7 +341,7 @@ NOTE: Please note that joins (DBRef's) are not supported with Reactive MongoDB s
======
[[mongodb.repositories.queries.full-text]]
-=== Full-text Search Queries
+== Full-text Search Queries
MongoDB's full-text search feature is store-specific and, therefore, can be found on `MongoRepository` rather than on the more general `CrudRepository`.
We need a document with a full-text index (see "`xref:mongodb/mapping/mapping.adoc#mapping-usage-indexes.text-index[Text Indexes]`" to learn how to create a full-text index).
@@ -944,230 +384,3 @@ criteria = TextCriteria.forDefaultLanguage().matching("film");
Page page = repository.findAllBy(criteria, PageRequest.of(1, 1, sort));
List result = repository.findByTitleOrderByScoreDesc("mongodb", criteria);
----
-
-
-[[mongodb.repositories.queries.update]]
-=== Repository Update Methods
-
-You can also use the keywords in the preceding table to create queries that identify matching documents for running updates on them.
-The actual update action is defined by the `@Update` annotation on the method itself, as the following listing shows.
-Note that the naming schema for derived queries starts with `find`.
-Using `update` (as in `updateAllByLastname(...)`) is allowed only in combination with `@Query`.
-
-The update is applied to *all* matching documents and it is *not* possible to limit the scope by passing in a `Page` or by using any of the <>.
-The return type can be either `void` or a _numeric_ type, such as `long`, to hold the number of modified documents.
-
-.Update Methods
-====
-[source,java]
-----
-public interface PersonRepository extends CrudRepository {
-
- @Update("{ '$inc' : { 'visits' : 1 } }")
- long findAndIncrementVisitsByLastname(String lastname); <1>
-
- @Update("{ '$inc' : { 'visits' : ?1 } }")
- void findAndIncrementVisitsByLastname(String lastname, int increment); <2>
-
- @Update("{ '$inc' : { 'visits' : ?#{[1]} } }")
- long findAndIncrementVisitsUsingSpELByLastname(String lastname, int increment); <3>
-
- @Update(pipeline = {"{ '$set' : { 'visits' : { '$add' : [ '$visits', ?1 ] } } }"})
- void findAndIncrementVisitsViaPipelineByLastname(String lastname, int increment); <4>
-
- @Update("{ '$push' : { 'shippingAddresses' : ?1 } }")
- long findAndPushShippingAddressByEmail(String email, Address address); <5>
-
- @Query("{ 'lastname' : ?0 }")
- @Update("{ '$inc' : { 'visits' : ?1 } }")
- void updateAllByLastname(String lastname, int increment); <6>
-}
-----
-
-<1> The filter query for the update is derived from the method name.
-The update is "`as is`" and does not bind any parameters.
-<2> The actual increment value is defined by the `increment` method argument that is bound to the `?1` placeholder.
-<3> Use the Spring Expression Language (SpEL) for parameter binding.
-<4> Use the `pipeline` attribute to issue xref:mongodb/template-crud-operations.adoc#mongo-template.aggregation-update[aggregation pipeline updates].
-<5> The update may contain complex objects.
-<6> Combine a xref:mongodb/repositories/repositories.adoc#mongodb.repositories.queries.json-based[string based query] with an update.
-====
-
-WARNING: Repository updates do not emit persistence nor mapping lifecycle events.
-
-[[mongodb.repositories.queries.delete]]
-=== Repository Delete Queries
-
-The keywords in the preceding table can be used in conjunction with `delete…By` or `remove…By` to create queries that delete matching documents.
-
-.`Delete…By` Query
-[tabs]
-======
-Imperative::
-+
-[source,java,indent=0,subs="verbatim,quotes",role="primary"]
-----
-public interface PersonRepository extends MongoRepository {
-
- List deleteByLastname(String lastname); <1>
-
- Long deletePersonByLastname(String lastname); <2>
-
- @Nullable
- Person deleteSingleByLastname(String lastname); <3>
-
- Optional deleteByBirthdate(Date birthdate); <4>
-}
-----
-<1> Using a return type of `List` retrieves and returns all matching documents before actually deleting them.
-<2> A numeric return type directly removes the matching documents, returning the total number of documents removed.
-<3> A single domain type result retrieves and removes the first matching document.
-<4> Same as in 3 but wrapped in an `Optional` type.
-
-Reactive::
-+
-[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
-----
-public interface PersonRepository extends ReactiveMongoRepository {
-
- Flux deleteByLastname(String lastname); <1>
-
- Mono deletePersonByLastname(String lastname); <2>
-
- Mono deleteSingleByLastname(String lastname); <3>
-}
-----
-<1> Using a return type of `Flux` retrieves and returns all matching documents before actually deleting them.
-<2> A numeric return type directly removes the matching documents, returning the total number of documents removed.
-<3> A single domain type result retrieves and removes the first matching document.
-======
-
-[[mongodb.repositories.queries.aggregation]]
-== Aggregation Repository Methods
-
-The repository layer offers means to interact with xref:mongodb/aggregation-framework.adoc[the aggregation framework] via annotated repository query methods.
-Similar to the xref:mongodb/repositories/repositories.adoc#mongodb.repositories.queries.json-based[JSON based queries], you can define a pipeline using the `org.springframework.data.mongodb.repository.Aggregation` annotation.
-The definition may contain simple placeholders like `?0` as well as link:{springDocsUrl}/core.html#expressions[SpEL expressions] `?#{ … }`.
-
-.Aggregating Repository Method
-====
-[source,java]
-----
-public interface PersonRepository extends CrudRepository {
-
- @Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
- List groupByLastnameAndFirstnames(); <1>
-
- @Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
- List groupByLastnameAndFirstnames(Sort sort); <2>
-
- @Aggregation("{ $group: { _id : $lastname, names : { $addToSet : ?0 } } }")
- List groupByLastnameAnd(String property); <3>
-
- @Aggregation("{ $group: { _id : $lastname, names : { $addToSet : ?0 } } }")
- Slice groupByLastnameAnd(String property, Pageable page); <4>
-
- @Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
- Stream groupByLastnameAndFirstnamesAsStream(); <5>
-
- @Aggregation("{ $group : { _id : null, total : { $sum : $age } } }")
- SumValue sumAgeUsingValueWrapper(); <6>
-
- @Aggregation("{ $group : { _id : null, total : { $sum : $age } } }")
- Long sumAge(); <7>
-
- @Aggregation("{ $group : { _id : null, total : { $sum : $age } } }")
- AggregationResults sumAgeRaw(); <8>
-
- @Aggregation("{ '$project': { '_id' : '$lastname' } }")
- List findAllLastnames(); <9>
-
- @Aggregation(pipeline = {
- "{ $group : { _id : '$author', books: { $push: '$title' } } }",
- "{ $out : 'authors' }"
- })
- void groupAndOutSkippingOutput(); <10>
-}
-----
-[source,java]
-----
-public class PersonAggregate {
-
- private @Id String lastname; <2>
- private List names;
-
- public PersonAggregate(String lastname, List names) {
- // ...
- }
-
- // Getter / Setter omitted
-}
-
-public class SumValue {
-
- private final Long total; <6> <8>
-
- public SumValue(Long total) {
- // ...
- }
-
- // Getter omitted
-}
-----
-<1> Aggregation pipeline to group first names by `lastname` in the `Person` collection returning these as `PersonAggregate`.
-<2> If `Sort` argument is present, `$sort` is appended after the declared pipeline stages so that it only affects the order of the final results after having passed all other aggregation stages.
-Therefore, the `Sort` properties are mapped against the methods return type `PersonAggregate` which turns `Sort.by("lastname")` into `{ $sort : { '_id', 1 } }` because `PersonAggregate.lastname` is annotated with `@Id`.
-<3> Replaces `?0` with the given value for `property` for a dynamic aggregation pipeline.
-<4> `$skip`, `$limit` and `$sort` can be passed on via a `Pageable` argument. Same as in <2>, the operators are appended to the pipeline definition. Methods accepting `Pageable` can return `Slice` for easier pagination.
-<5> Aggregation methods can return `Stream` to consume results directly from an underlying cursor. Make sure to close the stream after consuming it to release the server-side cursor by either calling `close()` or through `try-with-resources`.
-<6> Map the result of an aggregation returning a single `Document` to an instance of a desired `SumValue` target type.
-<7> Aggregations resulting in single document holding just an accumulation result like e.g. `$sum` can be extracted directly from the result `Document`.
-To gain more control, you might consider `AggregationResult` as method return type as shown in <7>.
-<8> Obtain the raw `AggregationResults` mapped to the generic target wrapper type `SumValue` or `org.bson.Document`.
-<9> Like in <6>, a single value can be directly obtained from multiple result ``Document``s.
-<10> Skips the output of the `$out` stage when return type is `void`.
-====
-
-In some scenarios, aggregations might require additional options, such as a maximum run time, additional log comments, or the permission to temporarily write data to disk.
-Use the `@Meta` annotation to set those options via `maxExecutionTimeMs`, `comment` or `allowDiskUse`.
-
-[source,java]
-----
-interface PersonRepository extends CrudRepository {
-
- @Meta(allowDiskUse = true)
- @Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
- List groupByLastnameAndFirstnames();
-}
-----
-
-Or use `@Meta` to create your own annotation as shown in the sample below.
-
-[source,java]
-----
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ ElementType.METHOD })
-@Meta(allowDiskUse = true)
-@interface AllowDiskUse { }
-
-interface PersonRepository extends CrudRepository {
-
- @AllowDiskUse
- @Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
- List groupByLastnameAndFirstnames();
-}
-----
-
-[NOTE]
-====
-Simple-type single-result inspects the returned `Document` and checks for the following:
-
-. Only one entry in the document, return it.
-. Two entries, one is the `_id` value. Return the other.
-. Return for the first value assignable to the return type.
-. Throw an exception if none of the above is applicable.
-====
-
-WARNING: The `Page` return type is not supported for repository methods using `@Aggregation`. However, you can use a
-`Pageable` argument to add `$skip`, `$limit` and `$sort` to the pipeline and let the method return `Slice`.
-
diff --git a/src/main/antora/modules/ROOT/pages/mongodb/template-collection-management.adoc b/src/main/antora/modules/ROOT/pages/mongodb/template-collection-management.adoc
index 4e78dc607..00b3f8216 100644
--- a/src/main/antora/modules/ROOT/pages/mongodb/template-collection-management.adoc
+++ b/src/main/antora/modules/ROOT/pages/mongodb/template-collection-management.adoc
@@ -82,7 +82,7 @@ template.indexOps(Venue.class)
.ensureIndex(new GeospatialIndex("location"));
----
-NOTE: `Index` and `GeospatialIndex` support configuration of xref:mongodb/template-query-options.adoc#mongo.query.collation[collations].
+NOTE: `Index` and `GeospatialIndex` support configuration of xref:mongodb/template-query-operations.adoc#mongo.query.collation[collations].
[[mongo-template.index-and-collections.access]]
== Accessing Index Information
diff --git a/src/main/antora/modules/ROOT/pages/mongodb/template-config.adoc b/src/main/antora/modules/ROOT/pages/mongodb/template-config.adoc
index 25739a8fb..ac0189454 100644
--- a/src/main/antora/modules/ROOT/pages/mongodb/template-config.adoc
+++ b/src/main/antora/modules/ROOT/pages/mongodb/template-config.adoc
@@ -1,5 +1,5 @@
[[mongo-template.instantiating]]
-= MongoTemplate Configuration
+= Configuration
You can use the following configuration to create and register an instance of `MongoTemplate`, as the following example shows:
@@ -68,7 +68,7 @@ Other optional properties that you might like to set when creating a `MongoTempl
[[mongo-template.read-preference]]
== Default Read Preference
-The default read preference applied to read operations if no other preference was defined via the xref:mongodb/template-query-options.adoc#mongo.query.read-preference[Query].
+The default read preference applied to read operations if no other preference was defined via the xref:mongodb/template-query-operations.adoc#mongo.query.read-preference[Query].
[[mongo-template.writeresultchecking]]
== WriteResultChecking Policy
@@ -115,7 +115,7 @@ public class MyAppWriteConcernResolver implements WriteConcernResolver {
[[mongo-template.entity-lifecycle-events]]
== Publish entity lifecycle events
-The template publishes xref:mongodb/mapping/lifecycle-events.adoc#mongodb.mapping-usage.events[lifecycle events].
+The template publishes xref:mongodb/lifecycle-events.adoc#mongodb.mapping-usage.events[lifecycle events].
In case there are no listeners present, this feature can be disabled.
[source,java]
@@ -131,7 +131,7 @@ MongoOperations mongoTemplate(MongoClient mongoClient) {
[[mongo-template.entity-callbacks-config]]
== Configure EntityCallbacks
-Nest to lifecycle events the template invokes xref:mongodb/mapping/entity-callbacks.adoc[EntityCallbacks] which can be (if not auto configured) set via the template API.
+Nest to lifecycle events the template invokes xref:mongodb/lifecycle-events.adoc#mongo.entity-callbacks[EntityCallbacks] which can be (if not auto configured) set via the template API.
[tabs]
======
diff --git a/src/main/antora/modules/ROOT/pages/mongodb/template-crud-operations.adoc b/src/main/antora/modules/ROOT/pages/mongodb/template-crud-operations.adoc
index 5366c17ea..c9d26289f 100644
--- a/src/main/antora/modules/ROOT/pages/mongodb/template-crud-operations.adoc
+++ b/src/main/antora/modules/ROOT/pages/mongodb/template-crud-operations.adoc
@@ -9,7 +9,7 @@ Consider the following class:
[source,java]
----
-include::example$Person.java[tags=class]
+include::example$example/Person.java[tags=class]
----
Given the `Person` class in the preceding example, you can save, update and delete the object, as the following example shows:
@@ -112,7 +112,7 @@ public class ReactiveMongoApplication {
The preceding example is meant to show the use of save, update, and remove operations on `MongoTemplate` / `ReactiveMongoTemplate` and not to show complex mapping functionality.
The query syntax used in the preceding example is explained in more detail in the section "`xref:mongodb/template-query-operations.adoc[Querying Documents]`".
-IMPORTANT: MongoDB requires that you have an `_id` field for all documents. Please refer to the xref:mongodb/template-id-handling.adoc[id handling] section for details on the special treatment of this field.
+IMPORTANT: MongoDB requires that you have an `_id` field for all documents. Please refer to the xref:mongodb/template-crud-operations.adoc[ID handling] section for details on the special treatment of this field.
IMPORTANT: MongoDB collections can contain documents that represent instances of a variety of types. Please refer to the xref:mongodb/converters-type-mapping.adoc[type mapping] for details.
@@ -178,6 +178,50 @@ A similar set of insert operations is also available:
* `void` *insert* `(Object objectToSave)`: Insert the object to the default collection.
* `void` *insert* `(Object objectToSave, String collectionName)`: Insert the object to the specified collection.
+[[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:
+
+. 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.
+
+The following outlines what type conversion, if any, is done on the property mapped to the `_id` document field when using the `MappingMongoConverter` (the default for `MongoTemplate`).
+
+. If possible, an `id` property or field declared as a `String` in the Java class is converted to and stored as an `ObjectId` by using a Spring `Converter`. Valid conversion rules are delegated to the MongoDB Java driver. If it cannot be converted to an `ObjectId`, then the value is stored as a string in the database.
+. An `id` property or field declared as `BigInteger` in the Java class is converted to and stored as an `ObjectId` by using a Spring `Converter`.
+
+If no field or property specified in the previous sets of rules is present in the Java class, an implicit `_id` file is generated by the driver but not mapped to a property or field of the Java class.
+
+When querying and updating, `MongoTemplate` uses the converter that corresponds to the preceding rules for saving documents so that field names and types used in your queries can match what is in your domain classes.
+
+Some environments require a customized approach to map `Id` values such as data stored in MongoDB that did not run through the Spring Data mapping layer. Documents can contain `_id` values that can be represented either as `ObjectId` or as `String`.
+Reading documents from the store back to the domain type works just fine. Querying for documents via their `id` can be cumbersome due to the implicit `ObjectId` conversion. Therefore documents cannot be retrieved that way.
+For those cases `@MongoId` provides more control over the actual id mapping attempts.
+
+.`@MongoId` mapping
+====
+[source,java]
+----
+public class PlainStringId {
+ @MongoId String id; <1>
+}
+
+public class PlainObjectId {
+ @MongoId ObjectId id; <2>
+}
+
+public class StringToObjectId {
+ @MongoId(FieldType.OBJECT_ID) String id; <3>
+}
+----
+<1> The id is treated as `String` without further conversion.
+<2> The id is treated as `ObjectId`.
+<3> The id is treated as `ObjectId` if the given `String` is a valid `ObjectId` hex, otherwise as `String`. Corresponds to `@Id` usage.
+====
+
+
[[mongo-template.save-insert.collection]]
=== Into Which Collection Are My Documents Saved?
@@ -246,7 +290,7 @@ Mono result = template.bulkOps(BulkMode.ORDERED, Person.class)
[NOTE]
====
Server performance of batch and bulk is identical.
-However bulk operations do not publish xref:mongodb/mapping/lifecycle-events.adoc[lifecycle events].
+However bulk operations do not publish xref:mongodb/lifecycle-events.adoc[lifecycle events].
====
[[mongodb-template-update]]
diff --git a/src/main/antora/modules/ROOT/pages/mongodb/template-id-handling.adoc b/src/main/antora/modules/ROOT/pages/mongodb/template-id-handling.adoc
deleted file mode 100644
index 966a23784..000000000
--- a/src/main/antora/modules/ROOT/pages/mongodb/template-id-handling.adoc
+++ /dev/null
@@ -1,42 +0,0 @@
-[[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:
-
-. 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.
-
-The following outlines what type conversion, if any, is done on the property mapped to the `_id` document field when using the `MappingMongoConverter` (the default for `MongoTemplate`).
-
-. If possible, an `id` property or field declared as a `String` in the Java class is converted to and stored as an `ObjectId` by using a Spring `Converter`. Valid conversion rules are delegated to the MongoDB Java driver. If it cannot be converted to an `ObjectId`, then the value is stored as a string in the database.
-. An `id` property or field declared as `BigInteger` in the Java class is converted to and stored as an `ObjectId` by using a Spring `Converter`.
-
-If no field or property specified in the previous sets of rules is present in the Java class, an implicit `_id` file is generated by the driver but not mapped to a property or field of the Java class.
-
-When querying and updating, `MongoTemplate` uses the converter that corresponds to the preceding rules for saving documents so that field names and types used in your queries can match what is in your domain classes.
-
-Some environments require a customized approach to map `Id` values such as data stored in MongoDB that did not run through the Spring Data mapping layer. Documents can contain `_id` values that can be represented either as `ObjectId` or as `String`.
-Reading documents from the store back to the domain type works just fine. Querying for documents via their `id` can be cumbersome due to the implicit `ObjectId` conversion. Therefore documents cannot be retrieved that way.
-For those cases `@MongoId` provides more control over the actual id mapping attempts.
-
-.`@MongoId` mapping
-====
-[source,java]
-----
-public class PlainStringId {
- @MongoId String id; <1>
-}
-
-public class PlainObjectId {
- @MongoId ObjectId id; <2>
-}
-
-public class StringToObjectId {
- @MongoId(FieldType.OBJECT_ID) String id; <3>
-}
-----
-<1> The id is treated as `String` without further conversion.
-<2> The id is treated as `ObjectId`.
-<3> The id is treated as `ObjectId` if the given `String` is a valid `ObjectId` hex, otherwise as `String`. Corresponds to `@Id` usage.
-====
diff --git a/src/main/antora/modules/ROOT/pages/mongodb/template-query-operations.adoc b/src/main/antora/modules/ROOT/pages/mongodb/template-query-operations.adoc
index e32c46199..4505e11a9 100644
--- a/src/main/antora/modules/ROOT/pages/mongodb/template-query-operations.adoc
+++ b/src/main/antora/modules/ROOT/pages/mongodb/template-query-operations.adoc
@@ -97,7 +97,7 @@ The `Criteria` class provides the following methods, all of which correspond to
* `Criteria` *sampleRate* `(double sampleRate)` Creates a criterion using the `$sampleRate` operator
* `Criteria` *size* `(int s)` Creates a criterion using the `$size` operator
* `Criteria` *type* `(int t)` Creates a criterion using the `$type` operator
-* `Criteria` *matchingDocumentStructure* `(MongoJsonSchema schema)` Creates a criterion using the `$jsonSchema` operator for xref:mongodb/template-collection-schema.adoc[JSON schema criteria]. `$jsonSchema` can only be applied on the top level of a query and not property specific. Use the `properties` attribute of the schema to match against nested fields.
+* `Criteria` *matchingDocumentStructure* `(MongoJsonSchema schema)` Creates a criterion using the `$jsonSchema` operator for xref:mongodb/mapping/mapping-schema.adoc[JSON schema criteria]. `$jsonSchema` can only be applied on the top level of a query and not property specific. Use the `properties` attribute of the schema to match against nested fields.
* `Criteria` *bits()* is the gateway to https://docs.mongodb.com/manual/reference/operator/query-bitwise/[MongoDB bitwise query operators] like `$bitsAllClear`.
The Criteria class also provides the following methods for geospatial queries.
@@ -200,6 +200,87 @@ query.fields()
`@Query(fields="…")` allows usage of expression field projections at `Repository` level as described in xref:mongodb/repositories/repositories.adoc#mongodb.repositories.queries.json-based[MongoDB JSON-based Query Methods and Field Restriction].
+[[mongo.query.additional-query-options]]
+== Additional Query Options
+
+MongoDB offers various ways of applying meta information, like a comment or a batch size, to a query.Using the `Query` API
+directly there are several methods for those options.
+
+[[mongo.query.hints]]
+=== Hints
+
+Index hints can be applied in two ways, using the index name or its field definition.
+
+====
+[source,java]
+----
+template.query(Person.class)
+ .matching(query("...").withHint("index-to-use"));
+
+template.query(Person.class)
+ .matching(query("...").withHint("{ firstname : 1 }"));
+----
+====
+
+[[mongo.query.cursor-size]]
+=== Cursor Batch Size
+
+The cursor batch size defines the number of documents to return in each response batch.
+====
+[source,java]
+----
+Query query = query(where("firstname").is("luke"))
+ .cursorBatchSize(100)
+----
+====
+
+[[mongo.query.collation]]
+=== Collations
+
+Using collations with collection operations is a matter of specifying a `Collation` instance in your query or operation options, as the following two examples show:
+
+====
+[source,java]
+----
+Collation collation = Collation.of("de");
+
+Query query = new Query(Criteria.where("firstName").is("Amél"))
+ .collation(collation);
+
+List results = template.find(query, Person.class);
+----
+====
+
+[[mongo.query.read-preference]]
+=== Read Preference
+
+The `ReadPreference` to use can be set directly on the `Query` object to be run as outlined below.
+
+====
+[source,java]
+----
+template.find(Person.class)
+ .matching(query(where(...)).withReadPreference(ReadPreference.secondary()))
+ .all();
+----
+====
+
+NOTE: The preference set on the `Query` instance will supersede the default `ReadPreference` of `MongoTemplate`.
+
+[[mongo.query.comment]]
+=== Comments
+
+Queries can be equipped with comments which makes them easier to look up in server logs.
+
+====
+[source,java]
+----
+template.find(Person.class)
+ .matching(query(where(...)).comment("Use the force luke!"))
+ .all();
+----
+====
+
[[mongo-template.query.distinct]]
== Query Distinct Values
@@ -741,7 +822,7 @@ Note that these two optional flags have been introduced in MongoDB 3.2 and are n
[[mongo.query-by-example]]
== Query by Example
-Some general information about Query By Example support in Spring Data can be found in the commons documentation.
+xref:repositories/query-by-example.adoc[Query by Example] can be used on the Template API level run example queries.
The following snipped shows how to query by example:
@@ -858,7 +939,7 @@ template.find(query(matchingDocumentStructure(schema)), Person.class);
----
====
-Please refer to the xref:mongodb/template-collection-schema.adoc[JSON Schema] section to learn more about the schema support in Spring Data MongoDB.
+Please refer to the xref:mongodb/mapping/mapping-schema.adoc[JSON Schema] section to learn more about the schema support in Spring Data MongoDB.
diff --git a/src/main/antora/modules/ROOT/pages/mongodb/template-query-options.adoc b/src/main/antora/modules/ROOT/pages/mongodb/template-query-options.adoc
deleted file mode 100644
index 02f14f696..000000000
--- a/src/main/antora/modules/ROOT/pages/mongodb/template-query-options.adoc
+++ /dev/null
@@ -1,81 +0,0 @@
-[[mongo.query.additional-query-options]]
-= Additional Query Options
-
-MongoDB offers various ways of applying meta information, like a comment or a batch size, to a query.Using the `Query` API
-directly there are several methods for those options.
-
-[[mongo.query.hints]]
-== Hints
-
-Index hints can be applied in two ways, using the index name or its field definition.
-
-====
-[source,java]
-----
-template.query(Person.class)
- .matching(query("...").withHint("index-to-use"));
-
-template.query(Person.class)
- .matching(query("...").withHint("{ firstname : 1 }"));
-----
-====
-
-[[mongo.query.cursor-size]]
-== Cursor Batch Size
-
-The cursor batch size defines the number of documents to return in each response batch.
-====
-[source,java]
-----
-Query query = query(where("firstname").is("luke"))
- .cursorBatchSize(100)
-----
-====
-
-[[mongo.query.collation]]
-== Collations
-
-Using collations with collection operations is a matter of specifying a `Collation` instance in your query or operation options, as the following two examples show:
-
-====
-[source,java]
-----
-Collation collation = Collation.of("de");
-
-Query query = new Query(Criteria.where("firstName").is("Amél"))
- .collation(collation);
-
-List results = template.find(query, Person.class);
-----
-====
-
-[[mongo.query.read-preference]]
-== Read Preference
-
-The `ReadPreference` to use can be set directly on the `Query` object to be run as outlined below.
-
-====
-[source,java]
-----
-template.find(Person.class)
- .matching(query(where(...)).withReadPreference(ReadPreference.secondary()))
- .all();
-----
-====
-
-NOTE: The preference set on the `Query` instance will supersede the default `ReadPreference` of `MongoTemplate`.
-
-[[mongo.query.comment]]
-== Comments
-
-Queries can be equipped with comments which makes them easier to look up in server logs.
-
-====
-[source,java]
-----
-template.find(Person.class)
- .matching(query(where(...)).comment("Use the force luke!"))
- .all();
-----
-====
-
diff --git a/src/main/antora/modules/ROOT/pages/repositories/core-concepts.adoc b/src/main/antora/modules/ROOT/pages/repositories/core-concepts.adoc
index 6e7938f21..1a4af7a60 100644
--- a/src/main/antora/modules/ROOT/pages/repositories/core-concepts.adoc
+++ b/src/main/antora/modules/ROOT/pages/repositories/core-concepts.adoc
@@ -1,6 +1,6 @@
include::{commons}@data-commons::page$repositories/core-concepts.adoc[]
-[[cassandra.entity-persistence.state-detection-strategies]]
+[[mongodb.entity-persistence.state-detection-strategies]]
include::{commons}@data-commons::page$is-new-state-detection.adoc[leveloffset=+1]
[NOTE]
diff --git a/src/main/antora/modules/ROOT/pages/repositories/projections.adoc b/src/main/antora/modules/ROOT/pages/repositories/projections.adoc
index 875f61abe..9302203c5 100644
--- a/src/main/antora/modules/ROOT/pages/repositories/projections.adoc
+++ b/src/main/antora/modules/ROOT/pages/repositories/projections.adoc
@@ -1,4 +1,2 @@
-[[cassandra.projections]]
-= Projections
-
-include::{commons}@data-commons::page$repositories/projections.adoc[leveloffset=+1]
+[[mongodb.projections]]
+include::{commons}@data-commons::page$repositories/projections.adoc[]
diff --git a/src/main/antora/modules/ROOT/pages/mongodb/query-by-example.adoc b/src/main/antora/modules/ROOT/pages/repositories/query-by-example.adoc
similarity index 86%
rename from src/main/antora/modules/ROOT/pages/mongodb/query-by-example.adoc
rename to src/main/antora/modules/ROOT/pages/repositories/query-by-example.adoc
index 93929bf91..6c07119c7 100644
--- a/src/main/antora/modules/ROOT/pages/mongodb/query-by-example.adoc
+++ b/src/main/antora/modules/ROOT/pages/repositories/query-by-example.adoc
@@ -1,7 +1,7 @@
-[[query-by-example.running]]
-= Running an Example
+include::{commons}@data-commons::query-by-example.adoc[]
-TODO: move this section to the repositories documentation
+[[query-by-example.running]]
+== Running an Example
The following example shows how to query by example when using a repository (of `Person` objects, in this case):
diff --git a/src/main/asciidoc/images/epub-cover.png b/src/main/asciidoc/images/epub-cover.png
deleted file mode 100644
index 7be1d08b3..000000000
Binary files a/src/main/asciidoc/images/epub-cover.png and /dev/null differ
diff --git a/src/main/asciidoc/images/epub-cover.svg b/src/main/asciidoc/images/epub-cover.svg
deleted file mode 100644
index 6399a6094..000000000
--- a/src/main/asciidoc/images/epub-cover.svg
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
diff --git a/src/main/asciidoc/images/jconsole.png b/src/main/asciidoc/images/jconsole.png
deleted file mode 100644
index 267aee8a4..000000000
Binary files a/src/main/asciidoc/images/jconsole.png and /dev/null differ