You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
640 lines
25 KiB
640 lines
25 KiB
[[mapping-chapter]] |
|
= Mapping |
|
|
|
Rich mapping support is provided by the `MongoMappingConverter`. `MongoMappingConverter` has a rich metadata model that provides a full feature set of functionality to map domain objects to MongoDB documents.The mapping metadata model is populated using annotations on your domain objects. However, the infrastructure is not limited to using annotations as the only source of metadata information. The `MongoMappingConverter` also allows you to map objects to documents without providing any additional metadata, by following a set of conventions. |
|
|
|
In this section we will describe the features of the `MongoMappingConverter`. How to use conventions for mapping objects to documents and how to override those conventions with annotation based mapping metadata. |
|
|
|
NOTE: `SimpleMongoConverter` has been deprecated in Spring Data MongoDB M3 as all of its functionality has been subsumed into `MappingMongoConverter`. |
|
|
|
[[mapping-conventions]] |
|
== Convention based Mapping |
|
|
|
`MongoMappingConverter` 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 `savingsAccount` collection name. |
|
* All nested objects are stored as nested objects in the document and *not* as DBRefs |
|
* The converter will use any Spring Converters registered with it to override the default mapping of object properties to document field/values. |
|
* The fields of an object are used to convert to and from fields in the document. Public JavaBean properties are not used. |
|
* You can have a single non-zero argument constructor whose constructor argument names match top level field names of document, that constructor will be used. Otherwise the zero arg constructor will be 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 `MongoMappingConverter` 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: |
|
|
|
* A field annotated with `@Id` (`org.springframework.data.annotation.Id`) will be mapped to the `_id` 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"] |
|
.Examples for the translation of `_id` field definitions |
|
|=== |
|
| Field definition |
|
| Resulting Id-Fieldname in MongoDB |
|
|
|
| `String` id |
|
| `_id` |
|
|
|
| `@Field` `String` id |
|
| `_id` |
|
|
|
| `@Field("x")` `String` id |
|
| `x` |
|
|
|
| `@Id` `String` x |
|
| `_id` |
|
|
|
| `@Field("x")` `@Id` `String` x |
|
| `_id` |
|
|=== |
|
|
|
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 MongoDBdriver. 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. |
|
* 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. |
|
|
|
When querying and updating `MongoTemplate` will use the converter to handle conversions of the `Query` and `Update` objects that correspond to the above rules for saving documents so field names and types used in your queries will be able to match what is in your domain classes. |
|
|
|
[[mapping-conversion]] |
|
== Data mapping and type conversion |
|
|
|
This section explain how types are mapped to a MongoDB representation and vice versa. Spring Data MongoDB supports all types that can be represented as BSON, MongoDB's internal document format. |
|
In addition to these types, Spring Data MongoDB provides a set of built-in converters to map additional types. You can provide your own converters to adjust type conversion, see <<mapping-explicit-converters>> for further details. |
|
|
|
[cols="3,1,6", options="header"] |
|
.Type |
|
|=== |
|
| Type |
|
| Type conversion |
|
| Sample |
|
|
|
| `String` |
|
| native |
|
| `{"firstname" : "Dave"}` |
|
|
|
| `double`, `Double`, `float`, `Float` |
|
| native |
|
| `{"weight" : 42.5}` |
|
|
|
| `int`, `Integer`, `short`, `Short` |
|
| native + |
|
32-bit integer |
|
| `{"height" : 42}` |
|
|
|
| `long`, `Long` |
|
| native + |
|
64-bit integer |
|
| `{"height" : 42}` |
|
|
|
| `Date`, `Timestamp` |
|
| native |
|
| `{"date" : ISODate("2019-11-12T23:00:00.809Z")}` |
|
|
|
| `byte[]` |
|
| native |
|
| `{"bin" : { "$binary" : "AQIDBA==", "$type" : "00" }}` |
|
|
|
| `java.util.UUID` (Legacy UUID) |
|
| native |
|
| `{"uuid" : { "$binary" : "MEaf1CFQ6lSphaa3b9AtlA==", "$type" : "03" }}` |
|
|
|
| `Date` |
|
| native |
|
| `{"date" : ISODate("2019-11-12T23:00:00.809Z")}` |
|
|
|
| `ObjectId` |
|
| native |
|
| `{"_id" : ObjectId("5707a2690364aba3136ab870")}` |
|
|
|
| Array, `List`, `BasicDBList` |
|
| native |
|
| `{"cookies" : [ … ]}` |
|
|
|
| `boolean`, `Boolean` |
|
| native |
|
| `{"active" : true}` |
|
|
|
| `null` |
|
| native |
|
| `{"value" : null}` |
|
|
|
| `Document` |
|
| native |
|
| `{"value" : { … }}` |
|
|
|
| `AtomicInteger` + |
|
calling `get()` before the actual conversion |
|
| converter + |
|
32-bit integer |
|
| `{"value" : "741" }` |
|
|
|
| `AtomicLong` + |
|
calling `get()` before the actual conversion |
|
| converter + |
|
64-bit integer |
|
| `{"value" : "741" }` |
|
|
|
| `BigInteger` |
|
| converter + |
|
`String` |
|
| `{"value" : "741" }` |
|
|
|
| `BigDecimal` |
|
| converter + |
|
`String` |
|
| `{"value" : "741.99" }` |
|
|
|
| `URL` |
|
| converter |
|
| `{"website" : "http://projects.spring.io/spring-data-mongodb/" }` |
|
|
|
| `Locale` |
|
| converter |
|
| `{"locale : "en_US" }` |
|
|
|
| `char`, `Character` |
|
| converter |
|
| `{"char" : "a" }` |
|
|
|
| `NamedMongoScript` |
|
| converter + |
|
`Code` |
|
| `{"_id" : "script name", value: (some javascript code)`} |
|
|
|
| `java.util.Currency` |
|
| converter |
|
| `{"currencyCode" : "EUR"}` |
|
|
|
| `LocalDate` + |
|
(Joda, Java 8, JSR310-BackPort) |
|
| converter |
|
| `{"date" : ISODate("2019-11-12T00:00:00.000Z")}` |
|
|
|
| `LocalDateTime`, `LocalTime`, `Instant` + |
|
(Joda, Java 8, JSR310-BackPort) |
|
| converter |
|
| `{"date" : ISODate("2019-11-12T23:00:00.809Z")}` |
|
|
|
| `DateTime` (Joda) |
|
| converter |
|
| `{"date" : ISODate("2019-11-12T23:00:00.809Z")}` |
|
|
|
| `DateMidnight` (Joda) |
|
| converter |
|
| `{"date" : ISODate("2019-11-12T00:00:00.000Z")}` |
|
|
|
| `ZoneId` (Java 8, JSR310-BackPort) |
|
| converter |
|
| `{"zoneId" : "ECT - Europe/Paris"}` |
|
|
|
| `Box` |
|
| converter |
|
| `{"box" : { "first" : { "x" : 1.0 , "y" : 2.0} , "second" : { "x" : 3.0 , "y" : 4.0}}` |
|
|
|
| `Polygon` |
|
| converter |
|
| `{"polygon" : { "points" : [ { "x" : 1.0 , "y" : 2.0} , { "x" : 3.0 , "y" : 4.0} , { "x" : 4.0 , "y" : 5.0}]}}` |
|
|
|
| `Circle` |
|
| converter |
|
| `{"circle" : { "center" : { "x" : 1.0 , "y" : 2.0} , "radius" : 3.0 , "metric" : "NEUTRAL"}}` |
|
|
|
| `Point` |
|
| converter |
|
| `{"point" : { "x" : 1.0 , "y" : 2.0}}` |
|
|
|
| `GeoJsonPoint` |
|
| converter |
|
| `{"point" : { "type" : "Point" , "coordinates" : [3.0 , 4.0] }}` |
|
|
|
| `GeoJsonMultiPoint` |
|
| converter |
|
| `{"geoJsonLineString" : {"type":"MultiPoint", "coordinates": [ [ 0 , 0 ], [ 0 , 1 ], [ 1 , 1 ] ] }}` |
|
|
|
| `Sphere` |
|
| converter |
|
| `{"sphere" : { "center" : { "x" : 1.0 , "y" : 2.0} , "radius" : 3.0 , "metric" : "NEUTRAL"}}` |
|
|
|
| `GeoJsonPolygon` |
|
| converter |
|
| `{"polygon" : { "type" : "Polygon", "coordinates" : [[ [ 0 , 0 ], [ 3 , 6 ], [ 6 , 1 ], [ 0 , 0 ] ]] }}` |
|
|
|
| `GeoJsonMultiPolygon` |
|
| converter |
|
| `{"geoJsonMultiPolygon" : { "type" : "MultiPolygon", "coordinates" : [ |
|
[ [ [ -73.958 , 40.8003 ] , [ -73.9498 , 40.7968 ] ] ], |
|
[ [ [ -73.973 , 40.7648 ] , [ -73.9588 , 40.8003 ] ] ] |
|
] }}` |
|
|
|
| `GeoJsonLineString` |
|
| converter |
|
| `{ "geoJsonLineString" : { "type" : "LineString", "coordinates" : [ [ 40 , 5 ], [ 41 , 6 ] ] }}` |
|
|
|
| `GeoJsonMultiLineString` |
|
| converter |
|
| `{"geoJsonLineString" : { "type" : "MultiLineString", coordinates: [ |
|
[ [ -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 `MongoMappingConverter` is created by default when creating a `MongoTemplate`. You can create your own instance of the `MappingMongoConverter` so as to tell it where to scan the classpath at startup your domain classes in order to extract metadata and construct indexes. Also, by creating your own instance you can register Spring converters to use for mapping specific classes to and from the database. |
|
|
|
You can configure the `MongoMappingConverter` as well as `com.mongodb.Mongo` and MongoTemplate either using Java or XML based metadata. Here is an example using Spring's Java based configuration |
|
|
|
.@Configuration class to configure MongoDB mapping support |
|
==== |
|
[source,java] |
|
---- |
|
@Configuration |
|
public class GeoSpatialAppConfig extends AbstractMongoConfiguration { |
|
|
|
@Bean |
|
public Mongo mongo() throws Exception { |
|
return new Mongo("localhost"); |
|
} |
|
|
|
@Override |
|
public String getDatabaseName() { |
|
return "database"; |
|
} |
|
|
|
@Override |
|
public String getMappingBasePackage() { |
|
return "com.bigbank.domain"; |
|
} |
|
|
|
// the following are optional |
|
|
|
|
|
@Bean |
|
@Override |
|
public CustomConversions customConversions() throws Exception { |
|
List<Converter<?, ?>> converterList = new ArrayList<Converter<?, ?>>(); |
|
converterList.add(new org.springframework.data.mongodb.test.PersonReadConverter()); |
|
converterList.add(new org.springframework.data.mongodb.test.PersonWriteConverter()); |
|
return new CustomConversions(converterList); |
|
} |
|
|
|
@Bean |
|
public LoggingEventListener<MongoMappingEvent> mappingEventsListener() { |
|
return new LoggingEventListener<MongoMappingEvent>(); |
|
} |
|
} |
|
---- |
|
==== |
|
|
|
`AbstractMongoConfiguration` requires you to implement methods that define a `com.mongodb.Mongo` as well as provide a database name. `AbstractMongoConfiguration` also has a method you can override named `getMappingBasePackage(…)` which tells the converter where to scan for classes annotated with the `@Document` annotation. |
|
|
|
You can add additional converters to the converter by overriding the method afterMappingMongoConverterCreation. Also shown in the above example is a `LoggingEventListener` which logs `MongoMappingEvent` s that are posted onto Spring's `ApplicationContextEvent` infrastructure. |
|
|
|
NOTE: AbstractMongoConfiguration will create a MongoTemplate instance and registered with the container under the name `mongoTemplate`. |
|
|
|
You can also override the method `UserCredentials getUserCredentials()` to provide the username and password information to connect to the database. |
|
|
|
Spring's MongoDB namespace enables you to easily enable mapping functionality in XML |
|
|
|
.XML schema to configure MongoDB mapping support |
|
==== |
|
[source,xml] |
|
---- |
|
<?xml version="1.0" encoding="UTF-8"?> |
|
<beans xmlns="http://www.springframework.org/schema/beans" |
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
|
xmlns:context="http://www.springframework.org/schema/context" |
|
xmlns:mongo="http://www.springframework.org/schema/data/mongo" |
|
xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd |
|
http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd |
|
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> |
|
|
|
<!-- Default bean name is 'mongo' --> |
|
<mongo:mongo host="localhost" port="27017"/> |
|
|
|
<mongo:db-factory dbname="database" mongo-ref="mongo"/> |
|
|
|
<!-- by default look for a Mongo object named 'mongo' - default name used for the converter is 'mappingConverter' --> |
|
<mongo:mapping-converter base-package="com.bigbank.domain"> |
|
<mongo:custom-converters> |
|
<mongo:converter ref="readConverter"/> |
|
<mongo:converter> |
|
<bean class="org.springframework.data.mongodb.test.PersonWriteConverter"/> |
|
</mongo:converter> |
|
</mongo:custom-converters> |
|
</mongo:mapping-converter> |
|
|
|
<bean id="readConverter" class="org.springframework.data.mongodb.test.PersonReadConverter"/> |
|
|
|
<!-- set the mapping converter to be used by the MongoTemplate --> |
|
<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate"> |
|
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/> |
|
<constructor-arg name="mongoConverter" ref="mappingConverter"/> |
|
</bean> |
|
|
|
<bean class="org.springframework.data.mongodb.core.mapping.event.LoggingEventListener"/> |
|
|
|
</beans> |
|
---- |
|
==== |
|
|
|
The `base-package` property tells it where to scan for classes annotated with the `@org.springframework.data.mongodb.core.mapping.Document` annotation. |
|
|
|
[[mapping-usage]] |
|
== Metadata based Mapping |
|
|
|
To take full advantage of the object mapping functionality inside the Spring Data/MongoDB support, you should annotate your mapped objects with the `@Document` annotation. Although it is not necessary for the mapping framework to have this annotation (your POJOs will be mapped correctly, even without any annotations), it allows the classpath scanner to find and pre-process your domain objects to extract the necessary metadata. If you don't use this annotation, your application will take a slight performance hit the first time you store a domain object because the mapping framework needs to build up its internal metadata model so it knows about the properties of your domain object and how to persist them. |
|
|
|
.Example domain object |
|
==== |
|
[source,java] |
|
---- |
|
package com.mycompany.domain; |
|
|
|
@Document |
|
public class Person { |
|
|
|
@Id |
|
private ObjectId id; |
|
|
|
@Indexed |
|
private Integer ssn; |
|
|
|
private String firstName; |
|
|
|
@Indexed |
|
private String lastName; |
|
} |
|
---- |
|
==== |
|
|
|
IMPORTANT: The `@Id` annotation tells the mapper which property you want to use for the MongoDB `_id` property and the `@Indexed` annotation tells the mapping framework to call `createIndex(…)` on that property of your document, making searches faster. |
|
|
|
IMPORTANT: Automatic index creation is only done for types annotated with `@Document`. |
|
|
|
[[mapping-usage-annotations]] |
|
=== Mapping annotation overview |
|
|
|
The MappingMongoConverter can use metadata to drive the mapping of objects to documents. An overview of the annotations is provided below |
|
|
|
* `@Id` - applied at the field level to mark the field used for identity purpose. |
|
* `@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 database will be stored. |
|
* `@DBRef` - applied at the field to indicate it is to be stored using a com.mongodb.DBRef. |
|
* `@Indexed` - applied at the field level to describe how to index the field. |
|
* `@CompoundIndex` - 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. |
|
* `@Language` - applied at the field level to set the language override property for text index. |
|
* `@Transient` - by default all private fields are mapped to the document, this annotation excludes the field where it is applied from being stored in the database |
|
* `@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 and described the name of the field as it will be represented in the MongoDB BSON document thus allowing the name to be different than the fieldname of the class. |
|
* `@Version` - applied at field level is used for optimistic locking and checked for modification on save operations. The initial value is `zero` 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. |
|
|
|
Here is an example of a more complex mapping. |
|
|
|
[source,java] |
|
---- |
|
@Document |
|
@CompoundIndexes({ |
|
@CompoundIndex(name = "age_idx", def = "{'lastName': 1, 'age': -1}") |
|
}) |
|
public class Person<T extends Address> { |
|
|
|
@Id |
|
private String id; |
|
|
|
@Indexed(unique = true) |
|
private Integer ssn; |
|
|
|
@Field("fName") |
|
private String firstName; |
|
|
|
@Indexed |
|
private String lastName; |
|
|
|
private Integer age; |
|
|
|
@Transient |
|
private Integer accountTotal; |
|
|
|
@DBRef |
|
private List<Account> accounts; |
|
|
|
private T address; |
|
|
|
|
|
public Person(Integer ssn) { |
|
this.ssn = ssn; |
|
} |
|
|
|
@PersistenceConstructor |
|
public Person(Integer ssn, String firstName, String lastName, Integer age, T address) { |
|
this.ssn = ssn; |
|
this.firstName = firstName; |
|
this.lastName = lastName; |
|
this.age = age; |
|
this.address = address; |
|
} |
|
|
|
public String getId() { |
|
return id; |
|
} |
|
|
|
// no setter for Id. (getter is only exposed for some unit testing) |
|
|
|
public Integer getSsn() { |
|
return ssn; |
|
} |
|
|
|
// other getters/setters omitted |
|
---- |
|
|
|
[[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: |
|
|
|
* 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. |
|
* Otherwise a `MappingException` will be thrown indicating that the given constructor parameter could not be bound. |
|
|
|
[source,java] |
|
---- |
|
class OrderItem { |
|
|
|
private @Id String id; |
|
private int quantity; |
|
private double unitPrice; |
|
|
|
OrderItem(String id, @Value("#root.qty ?: 0") int quantity, double unitPrice) { |
|
this.id = id; |
|
this.quantity = quantity; |
|
this.unitPrice = unitPrice; |
|
} |
|
|
|
// getters/setters ommitted |
|
} |
|
|
|
Document input = new Document("id", "4711"); |
|
input.put("unitPrice", 2.5); |
|
input.put("qty",5); |
|
OrderItem item = converter.read(OrderItem.class, input); |
|
---- |
|
|
|
NOTE: The SpEL expression in the `@Value` annotation of the `quantity` parameter falls back to the value `0` if the given property path cannot be resolved. |
|
|
|
Additional examples for using the `@PersistenceConstructor` annotation can be found in the https://github.com/spring-projects/spring-data-mongodb/blob/master/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java[MappingMongoConverterUnitTests] test suite. |
|
|
|
[[mapping-usage-indexes.compound-index]] |
|
=== Compound Indexes |
|
|
|
Compound indexes are also supported. They are defined at the class level, rather than on individual properties. |
|
|
|
NOTE: Compound indexes are very important to improve the performance of queries that involve criteria on multiple fields |
|
|
|
Here's an example that creates a compound index of `lastName` in ascending order and `age` in descending order: |
|
|
|
.Example Compound Index Usage |
|
==== |
|
[source,java] |
|
---- |
|
package com.mycompany.domain; |
|
|
|
@Document |
|
@CompoundIndexes({ |
|
@CompoundIndex(name = "age_idx", def = "{'lastName': 1, 'age': -1}") |
|
}) |
|
public class Person { |
|
|
|
@Id |
|
private ObjectId id; |
|
private Integer age; |
|
private String firstName; |
|
private String lastName; |
|
|
|
} |
|
---- |
|
==== |
|
|
|
[[mapping-usage-indexes.text-index]] |
|
=== Text Indexes |
|
|
|
NOTE: The text index feature is disabled by default for mongodb v.2.4. |
|
|
|
Creating a text index allows accumulating several fields into a searchable full text index. It is only possible to have one text index per collection so all fields marked with `@TextIndexed` are combined into this index. Properties can be weighted to influence document score for ranking results. The default language for the text index is english, to change the default language set `@Document(language="spanish")` to any language you want. Using a property called `language` or `@Language` allows to define a language override on a per document base. |
|
|
|
.Example Text Index Usage |
|
==== |
|
[source,java] |
|
---- |
|
@Document(language = "spanish") |
|
class SomeEntity { |
|
|
|
@TextIndexed String foo; |
|
|
|
@Language String lang; |
|
|
|
Nested nested; |
|
} |
|
|
|
class Nested { |
|
|
|
@TextIndexed(weight=5) String bar; |
|
String roo; |
|
} |
|
---- |
|
==== |
|
|
|
[[mapping-usage-references]] |
|
=== Using DBRefs |
|
|
|
The mapping framework doesn't have to store child objects embedded within the document. You can also store them separately and use a DBRef to refer to that document. When the object is loaded from MongoDB, those references will be eagerly resolved and you will get back a mapped object that looks the same as if it had been stored embedded within your master document. |
|
|
|
Here's an example of using a DBRef to refer to a specific document that exists independently of the object in which it is referenced (both classes are shown in-line for brevity's sake): |
|
|
|
==== |
|
[source,java] |
|
---- |
|
@Document |
|
public class Account { |
|
|
|
@Id |
|
private ObjectId id; |
|
private Float total; |
|
} |
|
|
|
@Document |
|
public class Person { |
|
|
|
@Id |
|
private ObjectId id; |
|
@Indexed |
|
private Integer ssn; |
|
@DBRef |
|
private List<Account> accounts; |
|
} |
|
---- |
|
==== |
|
|
|
There's no need to use something like `@OneToMany` because the mapping framework sees that you want a one-to-many relationship because there is a List of objects. When the object is stored in MongoDB, there will be a list of DBRefs rather than the `Account` objects themselves. |
|
|
|
IMPORTANT: The mapping framework does not handle cascading saves. If you change an `Account` object that is referenced by a `Person` object, you must save the Account object separately. Calling `save` on the `Person` object will not automatically save the `Account` objects in the property `accounts`. |
|
|
|
[[mapping-usage-events]] |
|
=== Mapping Framework Events |
|
|
|
Events are fired throughout the lifecycle of the mapping process. This is described in the <<mongodb.mapping-usage.events,Lifecycle Events>> section. |
|
|
|
Simply declaring these beans in your Spring ApplicationContext will cause them to be invoked whenever the event is dispatched. |
|
|
|
[[mapping-explicit-converters]] |
|
=== Overriding Mapping with explicit Converters |
|
|
|
When storing and querying your objects it is convenient to have a `MongoConverter` instance handle the mapping of all Java types to Documents. However, sometimes you may want the `MongoConverter` s do most of the work but allow you to selectively handle the conversion for a particular type or to optimize performance. |
|
|
|
To selectively handle the conversion yourself, register one or more one or more `org.springframework.core.convert.converter.Converter` instances with the MongoConverter. |
|
|
|
NOTE: Spring 3.0 introduced a core.convert package that provides a general type conversion system. This is described in detail in the Spring reference documentation section entitled http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/validation.html#core-convert[Spring Type Conversion]. |
|
|
|
The method `customConversions` in `AbstractMongoConfiguration` can be used to configure Converters. The examples <<mapping-configuration,here>> at the beginning of this chapter show how to perform the configuration using Java and XML. |
|
|
|
Below is an example of a Spring Converter implementation that converts from a Document to a Person POJO. |
|
|
|
[source,java] |
|
---- |
|
@ReadingConverter |
|
public class PersonReadConverter implements Converter<Document, Person> { |
|
|
|
public Person convert(Document source) { |
|
Person p = new Person((ObjectId) source.get("_id"), (String) source.get("name")); |
|
p.setAge((Integer) source.get("age")); |
|
return p; |
|
} |
|
} |
|
---- |
|
|
|
Here is an example that converts from a Person to a Document. |
|
|
|
[source,java] |
|
---- |
|
@WritingConverter |
|
public class PersonWriteConverter implements Converter<Person, Document> { |
|
|
|
public Document convert(Person source) { |
|
Document document = new Document(); |
|
document.put("_id", source.getId()); |
|
document.put("name", source.getFirstName()); |
|
document.put("age", source.getAge()); |
|
return document; |
|
} |
|
} |
|
----
|
|
|