Polishing.

Replace duplicate occurrences of _id with FieldName.ID.name(). Shorten property names to avoid repetative "field" wording. Add Javadoc
to MongoField builder.

See #4464
Original pull request: #4512
This commit is contained in:
Mark Paluch
2023-10-09 14:26:54 +02:00
parent 691fc055ed
commit 9c09edd594
30 changed files with 330 additions and 167 deletions

View File

@@ -36,7 +36,7 @@ The following outlines what field will be mapped to the `_id` document field:
* A field without an annotation but named `id` will be mapped to the `_id` field.
* The default field name for identifiers is `_id` and can be customized via the `@Field` annotation.
[cols="1,2", options="header"]
[cols="1,2",options="header"]
.Examples for the translation of `_id` field definitions
|===
| Field definition
@@ -60,9 +60,16 @@ The following outlines what field will be mapped to the `_id` document field:
The following outlines what type conversion, if any, will be done on the property mapped to the _id document field.
* If a field named `id` is declared as a String or BigInteger in the Java class it will be converted to and stored as an ObjectId if possible. ObjectId as a field type is also valid. If you specify a value for `id` in your application, the conversion to an ObjectId is detected to the MongoDB driver. If the specified `id` value cannot be converted to an ObjectId, then the value will be stored as is in the document's _id field. This also applies if the field is annotated with `@Id`.
* If a field is annotated with `@MongoId` in the Java class it will be converted to and stored as using its actual type. No further conversion happens unless `@MongoId` declares a desired field type. If no value is provided for the `id` field, a new `ObjectId` will be created and converted to the properties type.
* If a field is annotated with `@MongoId(FieldType.…)` in the Java class it will be attempted to convert the value to the declared `FieldType`. If no value is provided for the `id` field, a new `ObjectId` will be created and converted to the declared type.
* If a field named `id` is declared as a String or BigInteger in the Java class it will be converted to and stored as an ObjectId if possible.
ObjectId as a field type is also valid.
If you specify a value for `id` in your application, the conversion to an ObjectId is detected to the MongoDB driver.
If the specified `id` value cannot be converted to an ObjectId, then the value will be stored as is in the document's _id field.
This also applies if the field is annotated with `@Id`.
* If a field is annotated with `@MongoId` in the Java class it will be converted to and stored as using its actual type.
No further conversion happens unless `@MongoId` declares a desired field type.
If no value is provided for the `id` field, a new `ObjectId` will be created and converted to the properties type.
* If a field is annotated with `@MongoId(FieldType.…)` in the Java class it will be attempted to convert the value to the declared `FieldType`.
If no value is provided for the `id` field, a new `ObjectId` will be created and converted to the declared type.
* If a field named `id` id field is not declared as a String, BigInteger, or ObjectID in the Java class then you should assign it a value in your application so it can be stored 'as-is' in the document's _id field.
* If no field named `id` is present in the Java class then an implicit `_id` file will be generated by the driver but not mapped to a property or field of the Java class.
@@ -79,7 +86,7 @@ See xref:mongodb/mapping/custom-conversions.adoc[Custom Conversions - Overriding
.Built in Type conversions:
[%collapsible]
====
[cols="3,1,6", options="header"]
[cols="3,1,6",options="header"]
.Type
|===
| Type
@@ -250,9 +257,9 @@ calling `get()` before the actual conversion
| `GeoJsonMultiPolygon`
| converter
| `{"geoJsonMultiPolygon" : { "type" : "MultiPolygon", "coordinates" : [
[ [ [ -73.958 , 40.8003 ] , [ -73.9498 , 40.7968 ] ] ],
[ [ [ -73.973 , 40.7648 ] , [ -73.9588 , 40.8003 ] ] ]
] }}`
[ [ [ -73.958 , 40.8003 ] , [ -73.9498 , 40.7968 ] ] ],
[ [ [ -73.973 , 40.7648 ] , [ -73.9588 , 40.8003 ] ] ]
] }}`
| `GeoJsonLineString`
| converter
@@ -261,19 +268,22 @@ calling `get()` before the actual conversion
| `GeoJsonMultiLineString`
| converter
| `{"geoJsonLineString" : { "type" : "MultiLineString", coordinates: [
[ [ -73.97162 , 40.78205 ], [ -73.96374 , 40.77715 ] ],
[ [ -73.97880 , 40.77247 ], [ -73.97036 , 40.76811 ] ]
] }}`
[ [ -73.97162 , 40.78205 ], [ -73.96374 , 40.77715 ] ],
[ [ -73.97880 , 40.77247 ], [ -73.97036 , 40.76811 ] ]
] }}`
|===
====
[[mapping-configuration]]
== Mapping Configuration
Unless explicitly configured, an instance of `MappingMongoConverter` is created by default when you create a `MongoTemplate`. You can create your own instance of the `MappingMongoConverter`. Doing so lets you dictate where in the classpath your domain classes can be found, so that Spring Data MongoDB can extract metadata and construct indexes. Also, by creating your own instance, you can register Spring converters to map specific classes to and from the database.
You can configure the `MappingMongoConverter` as well as `com.mongodb.client.MongoClient` and MongoTemplate by using either Java-based or XML-based metadata. The following example shows the configuration:
Unless explicitly configured, an instance of `MappingMongoConverter` is created by default when you create a `MongoTemplate`.
You can create your own instance of the `MappingMongoConverter`.
Doing so lets you dictate where in the classpath your domain classes can be found, so that Spring Data MongoDB can extract metadata and construct indexes.
Also, by creating your own instance, you can register Spring converters to map specific classes to and from the database.
You can configure the `MappingMongoConverter` as well as `com.mongodb.client.MongoClient` and MongoTemplate by using either Java-based or XML-based metadata.
The following example shows the configuration:
[tabs]
======
@@ -309,7 +319,9 @@ public class MongoConfig extends AbstractMongoClientConfiguration {
}
}
----
<1> The mapping base package defines the root path used to scan for entities used to pre initialize the `MappingContext`. By default the configuration classes package is used.
<1> The mapping base package defines the root path used to scan for entities used to pre initialize the `MappingContext`.
By default the configuration classes package is used.
<2> Configure additional custom converters for specific domain types that replace the default mapping procedure for those types with your custom implementation.
XML::
@@ -363,7 +375,6 @@ Also shown in the preceding example is a `LoggingEventListener`, which logs `Mon
[TIP]
====
.Java Time Types
We recommend using MongoDB's native JSR-310 support via `MongoConverterConfigurationAdapter.useNativeDriverJavaTimeCodecs()` as described above as it is using an `UTC` based approach.
The default JSR-310 support for `java.time` types inherited from Spring Data Commons uses the local machine timezone as reference and should only be used for backwards compatibility.
====
@@ -418,26 +429,38 @@ WARNING: Auto index creation is **disabled** by default and needs to be enabled
[[mapping-usage-annotations]]
=== Mapping Annotation Overview
The MappingMongoConverter can use metadata to drive the mapping of objects to documents. The following annotations are available:
The MappingMongoConverter can use metadata to drive the mapping of objects to documents.
The following annotations are available:
* `@Id`: Applied at the field level to mark the field used for identity purpose.
* `@MongoId`: Applied at the field level to mark the field used for identity purpose. Accepts an optional `FieldType` to customize id conversion.
* `@Document`: Applied at the class level to indicate this class is a candidate for mapping to the database. You can specify the name of the collection where the data will be stored.
* `@MongoId`: Applied at the field level to mark the field used for identity purpose.
Accepts an optional `FieldType` to customize id conversion.
* `@Document`: Applied at the class level to indicate this class is a candidate for mapping to the database.
You can specify the name of the collection where the data will be stored.
* `@DBRef`: Applied at the field to indicate it is to be stored using a com.mongodb.DBRef.
* `@DocumentReference`: Applied at the field to indicate it is to be stored as a pointer to another document. This can be a single value (the _id_ by default), or a `Document` provided via a converter.
* `@DocumentReference`: Applied at the field to indicate it is to be stored as a pointer to another document.
This can be a single value (the _id_ by default), or a `Document` provided via a converter.
* `@Indexed`: Applied at the field level to describe how to index the field.
* `@CompoundIndex` (repeatable): Applied at the type level to declare Compound Indexes.
* `@GeoSpatialIndexed`: Applied at the field level to describe how to geoindex the field.
* `@TextIndexed`: Applied at the field level to mark the field to be included in the text index.
* `@HashIndexed`: Applied at the field level for usage within a hashed index to partition data across a sharded cluster.
* `@Language`: Applied at the field level to set the language override property for text index.
* `@Transient`: By default, all fields are mapped to the document. This annotation excludes the field where it is applied from being stored in the database. Transient properties cannot be used within a persistence constructor as the converter cannot materialize a value for the constructor argument.
* `@PersistenceConstructor`: Marks a given constructor - even a package protected one - to use when instantiating the object from the database. Constructor arguments are mapped by name to the key values in the retrieved Document.
* `@Value`: This annotation is part of the Spring Framework . Within the mapping framework it can be applied to constructor arguments. This lets you use a Spring Expression Language statement to transform a key's value retrieved in the database before it is used to construct a domain object. In order to reference a property of a given document one has to use expressions like: `@Value("#root.myProperty")` where `root` refers to the root of the given document.
* `@Transient`: By default, all fields are mapped to the document.
This annotation excludes the field where it is applied from being stored in the database.
Transient properties cannot be used within a persistence constructor as the converter cannot materialize a value for the constructor argument.
* `@PersistenceConstructor`: Marks a given constructor - even a package protected one - to use when instantiating the object from the database.
Constructor arguments are mapped by name to the key values in the retrieved Document.
* `@Value`: This annotation is part of the Spring Framework . Within the mapping framework it can be applied to constructor arguments.
This lets you use a Spring Expression Language statement to transform a key's value retrieved in the database before it is used to construct a domain object.
In order to reference a property of a given document one has to use expressions like: `@Value("#root.myProperty")` where `root` refers to the root of the given document.
* `@Field`: Applied at the field level it allows to describe the name and type of the field as it will be represented in the MongoDB BSON document thus allowing the name and type to be different than the fieldname of the class as well as the property type.
* `@Version`: Applied at field level is used for optimistic locking and checked for modification on save operations. The initial value is `zero` (`one` for primitive types) which is bumped automatically on every update.
* `@Version`: Applied at field level is used for optimistic locking and checked for modification on save operations.
The initial value is `zero` (`one` for primitive types) which is bumped automatically on every update.
The mapping metadata infrastructure is defined in a separate spring-data-commons project that is technology agnostic. Specific subclasses are using in the MongoDB support to support annotation based metadata. Other strategies are also possible to put in place if there is demand.
The mapping metadata infrastructure is defined in a separate spring-data-commons project that is technology agnostic.
Specific subclasses are using in the MongoDB support to support annotation based metadata.
Other strategies are also possible to put in place if there is demand.
.Here is an example of a more complex mapping
[%collapsible]
@@ -500,9 +523,9 @@ public class Person<T extends Address> {
[TIP]
====
`@Field(targetType=...)` can come in handy when the native MongoDB type inferred by the mapping infrastructure does not
match the expected one. Like for `BigDecimal`, which is represented as `String` instead of `Decimal128`, just because earlier
versions of MongoDB Server did not have support for it.
`@Field(targetType=...)` can come in handy when the native MongoDB type inferred by the mapping infrastructure does not match the expected one.
Like for `BigDecimal`, which is represented as `String` instead of `Decimal128`, just because earlier versions of MongoDB Server did not have support for it.
[source,java]
----
public class Balance {
@@ -537,22 +560,22 @@ public class Balance {
=== Special Field Names
Generally speaking MongoDB uses the `.` (dot) sign as a path deliminator for nested objects.
This means that in a query or update statement a key like `a.b.c` targets an object structure as outlined below.
Generally speaking MongoDB uses the dot (`.`) character as a path separator for nested documents or arrays.
This means that in a query (or update statement) a key like `a.b.c` targets an object structure as outlined below:
[source,json]
----
{
'a' : {
'b' : {
'c' : ...
'c' :
}
}
}
----
Therefore up until MongoDB 5.0 field names must not contain `.` (dot). +
Using a `MappingMongoConverter#setMapKeyDotReplacement` allowed circumvent some of the limitations when storing `Map` structures by substituting `.` (dots) on write with another character.
Therefore, up until MongoDB 5.0 field names must not contain dots (`.`). +
Using a `MappingMongoConverter#setMapKeyDotReplacement` allowed circumvent some of the limitations when storing `Map` structures by substituting dots on write with another character.
[source,java]
----
@@ -565,16 +588,18 @@ converter.write(source,...) // -> map : { 'key-with-dot', 'value' }
With the release of MongoDB 5.0 this restriction on `Document` field names containing special characters was lifted.
We highly recommend reading more about limitations on using dots in field names in the https://www.mongodb.com/docs/manual/core/dot-dollar-considerations/[MongoDB Reference]. +
To allow `.` (dots) in `Map` structures please set `preserveMapKeys` on the `MappingMongoConverter`.
To allow dots in `Map` structures please set `preserveMapKeys` on the `MappingMongoConverter`.
Using `@Field` allows to customize the field name to consider `.` (dots) in two ways.
Using `@Field` allows customizing the field name to consider dots in two ways.
. `@Field(name = "a.b")`: The name is considered to be a path. Writes will create nested objects such as `{ `a` : { `b` : ... } }`.
. `@Field(name = "a.b", fieldNameType = KEY)`: The names is considered a name as is. Writes will create a field with the given value as `{ 'a.b' : ... }`
. `@Field(name = "a.b")`: The name is considered to be a path.
Operations expect a structure of nested objects such as `{ a : { b : … } }`.
. `@Field(name = "a.b", fieldNameType = KEY)`: The names is considered a name as-is.
Operations expect a field with the given value as `{ 'a.b' : ….. }`
[WARNING]
====
Due to the special nature of the `.` (dot) sign in both MongoDB query and update statements field names containing `.` cannot be targeted directly and therefore are excluded from being used in derived query methods.
Due to the special nature of the dot character in both MongoDB query and update statements field names containing dots cannot be targeted directly and therefore are excluded from being used in derived query methods.
Consider the following `Item` having a `categoryId` property that is mapped to the field named `cat.id`.
[source,java]
@@ -588,7 +613,8 @@ public class Item {
}
----
It's raw representation will look like
Its raw representation will look like
[source,json]
----
{
@@ -599,7 +625,7 @@ It's raw representation will look like
Since we cannot target the `cat.id` field directly (as this would be interpreted as a path) we need the help of the xref:mongodb/aggregation-framework.adoc#mongo.aggregation[Aggregation Framework].
.Query fields with `.` (dot) in name
.Query fields with a dot in its name
[source,java]
----
template.query(Item.class)
@@ -607,9 +633,11 @@ template.query(Item.class)
.matching(expr(ComparisonOperators.valueOf(ObjectOperators.getValueOf("value")).equalToValue("5b28b5e7-52c2"))) <1>
.all();
----
<1> The mapping layer takes care of translating the property name `value` into the actual field name. It is absolutely valid to use the target field name here as well.
.Update fields with `.` (dot) in name
<1> The mapping layer takes care of translating the property name `value` into the actual field name.
It is absolutely valid to use the target field name here as well.
.Update fields with a dot in its name
[source,java]
----
template.update(Item.class)
@@ -618,18 +646,23 @@ template.update(Item.class)
.apply(AggregationUpdate.newUpdate(ReplaceWithOperation.replaceWithValue(ObjectOperators.setValueTo("value", "af29-f87f4e933f97")))) <1>
.first();
----
<1> The mapping layer takes care of translating the property name `value` into the actual field name. It is absolutely valid to use the target field name here as well.
The above shows a simple example where the special field is present on the top document level. Increased levels of nesting increase the complexity of the aggregation expression required to interact with the field.
<1> The mapping layer takes care of translating the property name `value` into the actual field name.
It is absolutely valid to use the target field name here as well.
The above shows a simple example where the special field is present on the top document level.
Increased levels of nesting increase the complexity of the aggregation expression required to interact with the field.
====
[[mapping-custom-object-construction]]
=== Customized Object Construction
The mapping subsystem allows the customization of the object construction by annotating a constructor with the `@PersistenceConstructor` annotation. The values to be used for the constructor parameters are resolved in the following way:
The mapping subsystem allows the customization of the object construction by annotating a constructor with the `@PersistenceConstructor` annotation.
The values to be used for the constructor parameters are resolved in the following way:
* If a parameter is annotated with the `@Value` annotation, the given expression is evaluated and the result is used as the parameter value.
* If the Java type has a property whose name matches the given field of the input document, then it's property information is used to select the appropriate constructor parameter to pass the input field value to. This works only if the parameter name information is present in the java `.class` files which can be achieved by compiling the source with debug information or using the new `-parameters` command-line switch for javac in Java 8.
* If the Java type has a property whose name matches the given field of the input document, then it's property information is used to select the appropriate constructor parameter to pass the input field value to.
This works only if the parameter name information is present in the java `.class` files which can be achieved by compiling the source with debug information or using the new `-parameters` command-line switch for javac in Java 8.
* Otherwise, a `MappingException` will be thrown indicating that the given constructor parameter could not be bound.
[source,java]