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 @@ - - - - - Spring Data MongoDB - Reference Guide - Mark Pollack, Thomas Risberg, - Oliver Gierke, Costin Leau, - Jon Brisbin, Thomas Darimont, - Christoph Strobl, Mark Paluch, - Jay Bryant - 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 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 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 @@ - - - - - Spring Data MongoDB - Reference Guide - Mark Pollack, Thomas Risberg, - Oliver Gierke, Costin Leau, - Jon Brisbin, Thomas Darimont, - Christoph Strobl, Mark Paluch, - Jay Bryant - 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