Restructure documentation.

Move examples into test source root to ensure proper compilation. Remove lingering asciidoc resources.
Merge entity callbacks into lifecycle events. Add links from MongoDB overview.

See: #4497
This commit is contained in:
Mark Paluch
2023-09-07 10:59:00 +02:00
committed by Christoph Strobl
parent 5acb174837
commit 7c50f991ed
32 changed files with 1114 additions and 1130 deletions

View File

@@ -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;

View File

@@ -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;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.9 KiB

View File

@@ -0,0 +1 @@
../../../../../../spring-data-mongodb/src/test/java/org/springframework/data/mongodb/example

View File

@@ -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[]

View File

@@ -1 +0,0 @@
// include::{commons}@data-commons::page$kotlin/object-mapping.adoc[]

View File

@@ -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.

View File

@@ -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]
----
======

View File

@@ -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`
|===

View File

@@ -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`.

View File

@@ -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`
|===

View File

@@ -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.

View File

@@ -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]

View File

@@ -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<Person, String> {
List<Person> findByLastname(String lastname); <1>
Page<Person> findByFirstname(String firstname, Pageable pageable); <2>
Person findByShippingAddresses(Address address); <3>
Person findFirstByLastname(String lastname); <4>
Stream<Person> 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<Person, String> {
Flux<Person> findByFirstname(String firstname); <1>
Flux<Person> findByFirstname(Publisher<String> firstname); <2>
Flux<Person> findByFirstnameOrderByLastname(String firstname, Pageable pageable); <3>
Mono<Person> findByFirstnameAndLastname(String firstname, String lastname); <4>
Mono<Person> 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<Page>`) 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<Person> 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<Integer> 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<Person, String> {
// { 'location' : { '$near' : [point.x, point.y], '$maxDistance' : distance}}
List<Person> findByLocationNear(Point location, Distance distance);
}
----
Reactive::
+
[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
----
interface PersonRepository extends ReactiveMongoRepository<Person, String> {
// { 'location' : { '$near' : [point.x, point.y], '$maxDistance' : distance}}
Flux<Person> 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<T>` 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<Person, String> {
// {'geoNear' : 'location', 'near' : [x, y] }
GeoResults<Person> 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<Person> findByLocationNear(Point location, Distance distance);
// Metric: {'geoNear' : 'person', 'near' : [x, y], 'minDistance' : min,
// 'maxDistance' : max, 'distanceMultiplier' : metric.multiplier,
// 'spherical' : true }
GeoResults<Person> findByLocationNear(Point location, Distance min, Distance max);
// {'geoNear' : 'location', 'near' : [x, y] }
GeoResults<Person> findByLocationNear(Point location);
}
----
Reactive::
+
[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
----
interface PersonRepository extends ReactiveMongoRepository<Person, String> {
// {'geoNear' : 'location', 'near' : [x, y] }
Flux<GeoResult<Person>> 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<GeoResult<Person>> findByLocationNear(Point location, Distance distance);
// Metric: {'geoNear' : 'person', 'near' : [x, y], 'minDistance' : min,
// 'maxDistance' : max, 'distanceMultiplier' : metric.multiplier,
// 'spherical' : true }
Flux<GeoResult<Person>> findByLocationNear(Point location, Distance min, Distance max);
// {'geoNear' : 'location', 'near' : [x, y] }
Flux<GeoResult<Person>> 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<Person, String> {
@Query("{ 'firstname' : ?0 }")
List<Person> findByThePersonsFirstname(String firstname);
}
----
Reactive::
+
[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
----
public interface PersonRepository extends ReactiveMongoRepository<Person, String> {
@Query("{ 'firstname' : ?0 }")
Flux<Person> 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<Person, String> {
@Query(value="{ 'firstname' : ?0 }", fields="{ 'firstname' : 1, 'lastname' : 1}")
List<Person> findByThePersonsFirstname(String firstname);
}
----
Reactive::
+
[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
----
public interface PersonRepository extends ReactiveMongoRepository<Person, String> {
@Query(value="{ 'firstname' : ?0 }", fields="{ 'firstname' : 1, 'lastname' : 1}")
Flux<Person> 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<Person, String> {
List<Person> findByFirstnameSortByAgeDesc(String firstname); <1>
List<Person> findByFirstname(String firstname, Sort sort); <2>
@Query(sort = "{ age : -1 }")
List<Person> findByFirstname(String firstname); <3>
@Query(sort = "{ age : -1 }")
List<Person> 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<Person, String> {
Flux<Person> findByFirstnameSortByAgeDesc(String firstname);
Flux<Person> findByFirstname(String firstname, Sort sort);
@Query(sort = "{ age : -1 }")
Flux<Person> findByFirstname(String firstname);
@Query(sort = "{ age : -1 }")
Flux<Person> 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<Person, String> {
@Query("{'lastname': ?#{[0]} }")
List<Person> findByQueryWithExpression(String param0);
}
----
Reactive::
+
[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
----
public interface PersonRepository extends ReactiveMongoRepository<Person, String> {
@Query("{'lastname': ?#{[0]} }")
Flux<Person> 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<Person, String> {
@Query("{'id': ?#{ [0] ? {$exists :true} : [1] }}")
List<Person> findByQueryWithExpressionAndNestedObject(boolean param0, String param1);
}
----
Reactive::
+
[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
----
public interface PersonRepository extends ReactiveMongoRepository<Person, String> {
@Query("{'id': ?#{ [0] ? {$exists :true} : [1] }}")
Flux<Person> 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<String, Object> getProperties() {
return Collections.singletonMap("principal", SecurityContextHolder.getCurrent().getPrincipal());
}
}
----
Reactive::
+
[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
----
public class SampleEvaluationContextExtension implements ReactiveEvaluationContextExtension {
@Override
public String getExtensionId() {
return "security";
}
@Override
public Mono<? extends EvaluationContextExtension> getExtension() {
return Mono.just(new EvaluationContextExtensionSupport() { ... });
}
}
----
======
NOTE: Bootstrapping `MongoRepositoryFactory` yourself is not application context-aware and requires further configuration to pick up Query SPI extensions.
NOTE: Reactive query methods can make use of `org.springframework.data.spel.spi.ReactiveEvaluationContextExtension`.
[[mongodb.repositories.queries.update]]
== Update Methods
You can also use the keywords in the preceding table to create queries that identify matching documents for running updates on them.
The actual update action is defined by the `@Update` annotation on the method itself, as the following listing shows.
Note that the naming schema for derived queries starts with `find`.
Using `update` (as in `updateAllByLastname(...)`) is allowed only in combination with `@Query`.
The update is applied to *all* matching documents and it is *not* possible to limit the scope by passing in a `Page` or by using any of the <<repositories.limit-query-result,limiting keywords>>.
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<Person, String> {
@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<Person, String> {
List <Person> deleteByLastname(String lastname); <1>
Long deletePersonByLastname(String lastname); <2>
@Nullable
Person deleteSingleByLastname(String lastname); <3>
Optional<Person> 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<Person, String> {
Flux<Person> deleteByLastname(String lastname); <1>
Mono<Long> deletePersonByLastname(String lastname); <2>
Mono<Person> 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<Person, String> {
@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
List<PersonAggregate> groupByLastnameAndFirstnames(); <1>
@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
List<PersonAggregate> groupByLastnameAndFirstnames(Sort sort); <2>
@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : ?0 } } }")
List<PersonAggregate> groupByLastnameAnd(String property); <3>
@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : ?0 } } }")
Slice<PersonAggregate> groupByLastnameAnd(String property, Pageable page); <4>
@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
Stream<PersonAggregate> 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<SumValue> sumAgeRaw(); <8>
@Aggregation("{ '$project': { '_id' : '$lastname' } }")
List<String> 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<String> names;
public PersonAggregate(String lastname, List<String> 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<Person, String> {
@Meta(allowDiskUse = true)
@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
List<PersonAggregate> 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<Person, String> {
@AllowDiskUse
@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
List<PersonAggregate> 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<Person> findByLastname(String lastname);
@Query(value = "{ 'firstname' : ?0 }", hint = "firstname-idx") <2>
List<Person> 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<Person, String> {
@Query(collation = "en_US") <1>
List<Person> findByFirstname(String firstname);
@Query(collation = "{ 'locale' : 'en_US' }") <2>
List<Person> findPersonByFirstname(String firstname);
@Query(collation = "?1") <3>
List<Person> findByFirstname(String firstname, Object collation);
@Query(collation = "{ 'locale' : '?1' }") <4>
List<Person> findByFirstname(String firstname, String collation);
List<Person> findByFirstname(String firstname, Collation collation); <5>
@Query(collation = "{ 'locale' : 'en_US' }")
List<Person> 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<Game, String> {
@Collation("en_GB") <2>
List<Game> findByTitle(String title);
@Collation("de_AT") <3>
@Query(collation="en_GB")
List<Game> findByDescriptionContaining(String keyword);
}
----
<1> Instead of `@Document(collation=...)`.
<2> Instead of `@Query(collation=...)`.
<3> Favors `@Collation` over meta usage.
====

View File

@@ -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<Person, String> {
@Query(collation = "en_US") <1>
List<Person> findByFirstname(String firstname);
@Query(collation = "{ 'locale' : 'en_US' }") <2>
List<Person> findPersonByFirstname(String firstname);
@Query(collation = "?1") <3>
List<Person> findByFirstname(String firstname, Object collation);
@Query(collation = "{ 'locale' : '?1' }") <4>
List<Person> findByFirstname(String firstname, String collation);
List<Person> findByFirstname(String firstname, Collation collation); <5>
@Query(collation = "{ 'locale' : 'en_US' }")
List<Person> 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<Game, String> {
@Collation("en_GB") <2>
List<Game> findByTitle(String title);
@Collation("de_AT") <3>
@Query(collation="en_GB")
List<Game> findByDescriptionContaining(String keyword);
}
----
<1> Instead of `@Document(collation=...)`.
<2> Instead of `@Query(collation=...)`.
<3> Favors `@Collation` over meta usage.
====

View File

@@ -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<Person> findByLastname(String lastname);
@Query(value = "{ 'firstname' : ?0 }", hint = "firstname-idx") <2>
List<Person> 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.

View File

@@ -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<Person, String> {
List<Person> findByLastname(String lastname); <1>
Page<Person> findByFirstname(String firstname, Pageable pageable); <2>
Person findByShippingAddresses(Address address); <3>
Person findFirstByLastname(String lastname); <4>
Stream<Person> 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<Person, String> {
Flux<Person> findByFirstname(String firstname); <1>
Flux<Person> findByFirstname(Publisher<String> firstname); <2>
Flux<Person> findByFirstnameOrderByLastname(String firstname, Pageable pageable); <3>
Mono<Person> findByFirstnameAndLastname(String firstname, String lastname); <4>
Mono<Person> 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<Page>`) 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<Person> 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<Integer> 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<Person, String> {
// { 'location' : { '$near' : [point.x, point.y], '$maxDistance' : distance}}
List<Person> findByLocationNear(Point location, Distance distance);
}
----
Reactive::
+
[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
----
interface PersonRepository extends ReactiveMongoRepository<Person, String> {
// { 'location' : { '$near' : [point.x, point.y], '$maxDistance' : distance}}
Flux<Person> 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<T>` 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<Person, String> {
// {'geoNear' : 'location', 'near' : [x, y] }
GeoResults<Person> 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<Person> findByLocationNear(Point location, Distance distance);
// Metric: {'geoNear' : 'person', 'near' : [x, y], 'minDistance' : min,
// 'maxDistance' : max, 'distanceMultiplier' : metric.multiplier,
// 'spherical' : true }
GeoResults<Person> findByLocationNear(Point location, Distance min, Distance max);
// {'geoNear' : 'location', 'near' : [x, y] }
GeoResults<Person> findByLocationNear(Point location);
}
----
Reactive::
+
[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
----
interface PersonRepository extends ReactiveMongoRepository<Person, String> {
// {'geoNear' : 'location', 'near' : [x, y] }
Flux<GeoResult<Person>> 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<GeoResult<Person>> findByLocationNear(Point location, Distance distance);
// Metric: {'geoNear' : 'person', 'near' : [x, y], 'minDistance' : min,
// 'maxDistance' : max, 'distanceMultiplier' : metric.multiplier,
// 'spherical' : true }
Flux<GeoResult<Person>> findByLocationNear(Point location, Distance min, Distance max);
// {'geoNear' : 'location', 'near' : [x, y] }
Flux<GeoResult<Person>> 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<Person, String> {
@Query("{ 'firstname' : ?0 }")
List<Person> findByThePersonsFirstname(String firstname);
}
----
Reactive::
+
[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
----
public interface PersonRepository extends ReactiveMongoRepository<Person, String> {
@Query("{ 'firstname' : ?0 }")
Flux<Person> 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<Person, String> {
@Query(value="{ 'firstname' : ?0 }", fields="{ 'firstname' : 1, 'lastname' : 1}")
List<Person> findByThePersonsFirstname(String firstname);
}
----
Reactive::
+
[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
----
public interface PersonRepository extends ReactiveMongoRepository<Person, String> {
@Query(value="{ 'firstname' : ?0 }", fields="{ 'firstname' : 1, 'lastname' : 1}")
Flux<Person> 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<Person, String> {
List<Person> findByFirstnameSortByAgeDesc(String firstname); <1>
List<Person> findByFirstname(String firstname, Sort sort); <2>
@Query(sort = "{ age : -1 }")
List<Person> findByFirstname(String firstname); <3>
@Query(sort = "{ age : -1 }")
List<Person> 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<Person, String> {
Flux<Person> findByFirstnameSortByAgeDesc(String firstname);
Flux<Person> findByFirstname(String firstname, Sort sort);
@Query(sort = "{ age : -1 }")
Flux<Person> findByFirstname(String firstname);
@Query(sort = "{ age : -1 }")
Flux<Person> 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<Person, String> {
@Query("{'lastname': ?#{[0]} }")
List<Person> findByQueryWithExpression(String param0);
}
----
Reactive::
+
[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
----
public interface PersonRepository extends ReactiveMongoRepository<Person, String> {
@Query("{'lastname': ?#{[0]} }")
Flux<Person> 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<Person, String> {
@Query("{'id': ?#{ [0] ? {$exists :true} : [1] }}")
List<Person> findByQueryWithExpressionAndNestedObject(boolean param0, String param1);
}
----
Reactive::
+
[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
----
public interface PersonRepository extends ReactiveMongoRepository<Person, String> {
@Query("{'id': ?#{ [0] ? {$exists :true} : [1] }}")
Flux<Person> 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<String, Object> getProperties() {
return Collections.singletonMap("principal", SecurityContextHolder.getCurrent().getPrincipal());
}
}
----
Reactive::
+
[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
----
public class SampleEvaluationContextExtension implements ReactiveEvaluationContextExtension {
@Override
public String getExtensionId() {
return "security";
}
@Override
public Mono<? extends EvaluationContextExtension> getExtension() {
return Mono.just(new EvaluationContextExtensionSupport() { ... });
}
}
----
======
NOTE: Bootstrapping `MongoRepositoryFactory` yourself is not application context-aware and requires further configuration to pick up Query SPI extensions.
NOTE: Reactive query methods can make use of `org.springframework.data.spel.spi.ReactiveEvaluationContextExtension`.
[[mongodb.repositories.queries.type-safe]]
=== Type-safe Query Methods
== Type-safe Query Methods
MongoDB repository and its reactive counterpart integrates with the http://www.querydsl.com/[Querydsl] project, which provides a way to perform type-safe queries.
@@ -901,7 +341,7 @@ NOTE: Please note that joins (DBRef's) are not supported with Reactive MongoDB s
======
[[mongodb.repositories.queries.full-text]]
=== Full-text Search Queries
== Full-text Search Queries
MongoDB's full-text search feature is store-specific and, therefore, can be found on `MongoRepository` rather than on the more general `CrudRepository`.
We need a document with a full-text index (see "`xref:mongodb/mapping/mapping.adoc#mapping-usage-indexes.text-index[Text Indexes]`" to learn how to create a full-text index).
@@ -944,230 +384,3 @@ criteria = TextCriteria.forDefaultLanguage().matching("film");
Page<FullTextDocument> page = repository.findAllBy(criteria, PageRequest.of(1, 1, sort));
List<FullTextDocument> 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 <<repositories.limit-query-result,limiting keywords>>.
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<Person, String> {
@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<Person, String> {
List <Person> deleteByLastname(String lastname); <1>
Long deletePersonByLastname(String lastname); <2>
@Nullable
Person deleteSingleByLastname(String lastname); <3>
Optional<Person> 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<Person, String> {
Flux<Person> deleteByLastname(String lastname); <1>
Mono<Long> deletePersonByLastname(String lastname); <2>
Mono<Person> 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<Person, String> {
@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
List<PersonAggregate> groupByLastnameAndFirstnames(); <1>
@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
List<PersonAggregate> groupByLastnameAndFirstnames(Sort sort); <2>
@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : ?0 } } }")
List<PersonAggregate> groupByLastnameAnd(String property); <3>
@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : ?0 } } }")
Slice<PersonAggregate> groupByLastnameAnd(String property, Pageable page); <4>
@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
Stream<PersonAggregate> 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<SumValue> sumAgeRaw(); <8>
@Aggregation("{ '$project': { '_id' : '$lastname' } }")
List<String> 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<String> names;
public PersonAggregate(String lastname, List<String> 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<Person, String> {
@Meta(allowDiskUse = true)
@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
List<PersonAggregate> 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<Person, String> {
@AllowDiskUse
@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
List<PersonAggregate> 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`.

View File

@@ -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

View File

@@ -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]
======

View File

@@ -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<String, ObjectId>`. 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<BigInteger, ObjectId>`.
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<BulkWriteResult> 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]]

View File

@@ -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<String, ObjectId>`. 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<BigInteger, ObjectId>`.
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.
====

View File

@@ -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<Person> 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.

View File

@@ -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<Person> 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();
----
====

View File

@@ -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]

View File

@@ -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[]

View File

@@ -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):

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB