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 | |
---|---|
|
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.
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 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.
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
Example 6.1. @Configuration class to configure MongoDB mapping support
@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
@org.springframework.data.mongodb.core.mapping.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
Example 6.2. XML schema to configure MongoDB mapping support
<?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.
To take full advantage of the object mapping functionality inside
the Spring Data/MongoDB support, you should annotate your mapped objects
with the
@org.springframework.data.mongodb.core.mapping.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 6.3. Example domain object
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 |
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 identiy 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.
@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
DBObject.
@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.
The mapping metadata infrastructure is defined in a seperate 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.
@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 ommitted
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 an MappingException
will be
thrown indicating that the given constructor parameter could not be
bound.
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 } DBObject input = new BasicDBObject("id", "4711"); input.put("unitPrice", 2.5); input.put("qty",5); OrderItem item = converter.read(OrderItem.class, input);
Note | |
---|---|
The SpEL expression in the |
Additional examples for using the
@PersistenceConstructor
annotation can be found
in the MappingMongoConverterUnitTests
test suite.
Compound indexes are also supported. They are defined at the class level, rather than on indidvidual 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 6.4. Example Compound Index Usage
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; }
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):
Example 6.5.
@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're wanting 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 |
Events are fired throughout the lifecycle of the mapping process. This is described in the Lifecycle Events section.
Simply declaring these beans in your Spring ApplicationContext will cause them to be invoked whenever the event is dispatched.
When storing and querying your objects it is convenient to have a
MongoConverter
instance handle the
mapping of all Java types to DBObjects. However, sometimes you may want
the MongoConverter
's do most of the work
but allow you to selectivly handle the conversion for a particular type
or to optimize performance.
To selectivly 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 Spring 3 Type Conversion. |
The method customConversions
in
AbstractMongoConfiguration
can be used to
configure Converters. The examples here at the begining 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 DBObject to a Person POJO.
@ReadingConverter public class PersonReadConverter implements Converter<DBObject, Person> { public Person convert(DBObject 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 DBObject.
@WritingConverter public class PersonWriteConverter implements Converter<Person, DBObject> { public DBObject convert(Person source) { DBObject dbo = new BasicDBObject(); dbo.put("_id", source.getId()); dbo.put("name", source.getFirstName()); dbo.put("age", source.getAge()); return dbo; } }