Chapter 11. MongoDB (original) (raw)
MongoDB is a document oriented datastore written in C++ with strong emphasis on ease of use. The nested nature of documents make it a particularly natural fit for most object representations.
This implementation is based upon the MongoDB Java driver. The currently supported version is 2.13.1.
Configuring Hibernate OGM to use MongoDb is easy:
- Add the MongoDB module and driver to the classpath
- provide the MongoDB URL to Hibernate OGM
To get started quickly, pay attention to the following options:
hibernate.ogm.datastore.provider
hibernate.ogm.datastore.host
hibernate.ogm.datastore.database
And we should have you running. The following properties are available to configure MongoDB support:
MongoDB datastore configuration properties
hibernate.ogm.datastore.provider
To use MongoDB as a datastore provider, this property must be set to mongodb
hibernate.ogm.option.configurator
The fully-qualified class name or an instance of a programmatic option configurator (see Section 11.1.5, “Programmatic configuration”)
hibernate.ogm.datastore.host
The hostname and port of the MongoDB instance. The optional port is concatenated to the host and separated by a colon. When using replica sets, you can define the various servers in a comma separated list of hosts and ports. Let’s see a few valid examples:
mongodb.example.com
mongodb.example.com:27018
2001:db8::ff00:42:8329
(IPv6)[2001:db8::ff00:42:8329]:27018
(IPv6 with port requires the IPv6 to be surrounded by square brackets)www.example.com, www2.example.com:123, 192.0.2.1, 192.0.2.2:123, 2001:db8::ff00:42:8329, [2001:db8::ff00:42:8329]:123
(replica set)
The default value is127.0.0.1:27017
. If left undefined, the default port is27017
.
hibernate.ogm.datastore.port
Deprecated: use hibernate.ogm.datastore.host
. The port used by the MongoDB instance. Ignored when multiple hosts are defined. The default value is 27017
.
hibernate.ogm.datastore.database
The database to connect to. This property has no default value.
hibernate.ogm.datastore.create_database
If set to true, the database will be created if it doesn’t exist. This property default value is false.
hibernate.ogm.datastore.username
The username used when connecting to the MongoDB server. This property has no default value.
hibernate.ogm.datastore.password
The password used to connect to the MongoDB server. This property has no default value. This property is ignored if the username isn’t specified.
hibernate.ogm.error_handler
The fully-qualified class name, class object or an instance of ErrorHandler
to get notified upon errors during flushes (see Section 6.3.1, “Acting upon errors during application of changes”)
hibernate.ogm.mongodb.connection_timeout
Defines the timeout used by the driver when the connection to the MongoDB instance is initiated. This configuration is expressed in milliseconds. The default value is 5000
.
hibernate.ogm.mongodb.authentication_mechanism
Define the authentication mechanism to use. Possible values are:
BEST
: Handshakes with the server to find the best authentication mechanism.SCRAM_SHA_1
: The SCRAM SHA 1 Challenge Response mechanism as described in this RFC.MONGODB_CR
: The MongoDB Challenge Response mechanism (deprecated since MongoDB 3)GSSAPI
: The GSSAPI mechanism. See the RFCMONGODB_X509
: The MongoDB X.509PLAIN
: The PLAIN mechanism. See the RFC
hibernate.ogm.datastore.document.association_storage
Defines the way OGM stores association information in MongoDB. The following two strategies exist (values of the org.hibernate.ogm.datastore.document.options.AssociationStorageType
enum):
IN_ENTITY
: store association information within the entityASSOCIATION_DOCUMENT
: store association information in a dedicated document per association
IN_ENTITY
is the default and recommended option unless the association navigation data is much bigger than the core of the document and leads to performance degradation.
hibernate.ogm.mongodb.association_document_storage
Defines how to store assocation documents (applies only if the ASSOCIATION_DOCUMENT
association storage strategy is used). Possible strategies are (values of the org.hibernate.ogm.datastore.mongodb.options.AssociationDocumentStorageType
enum):
GLOBAL_COLLECTION
(default): stores the association information in a unique MongoDB collection for all associationsCOLLECTION_PER_ASSOCIATION
stores the association in a dedicated MongoDB collection per association
hibernate.ogm.mongodb.write_concern
Defines the write concern setting to be applied when issuing writes against the MongoDB datastore. Possible settings are (values of the WriteConcernType
enum):ACKNOWLEDGED
, UNACKNOWLEDGED
, FSYNCED
, JOURNALED
, REPLICA_ACKNOWLEDGED
, MAJORITY
and CUSTOM
. When set to CUSTOM
, a custom WriteConcern
implementation type has to be specified.
This option is case insensitive and the default value is ACKNOWLEDGED
.
hibernate.ogm.mongodb.write_concern_type
Specifies a custom WriteConcern
implementation type (fully-qualified name, class object or instance). This is useful in cases where the pre-defined configurations are not sufficient, e.g. if you want to ensure that writes are propagated to a specific number of replicas or given "tag set". Only takes effect if hibernate.ogm.mongodb.write_concern
is set to CUSTOM
.
hibernate.ogm.mongodb.read_preference
Specifies the ReadPreference
to be applied when issuing reads against the MongoDB datastore. Possible settings are (values of the ReadPreferenceType
enum):PRIMARY
, PRIMARY_PREFERRED
, SECONDARY
, SECONDARY_PREFERRED
and NEAREST
. It’s currently not possible to plug in custom read preference types. If you’re interested in such a feature, please let us know.
For more information, please refer to theofficial documentation.
Note
When bootstrapping a session factory or entity manager factory programmatically, you should use the constants accessible via MongoDBProperties
when specifying the configuration properties listed above.
Common properties shared between stores are declared on OgmProperties
(a super interface of MongoDBProperties
).
For maximum portability between stores, use the most generic interface possible.
In addition to the annotation mechanism, Hibernate OGM also provides a programmatic API for applying store-specific configuration options. This can be useful if you can’t modify certain entity types or don’t want to add store-specific configuration annotations to them. The API allows set options in a type-safe fashion on the global, entity and property levels.
When working with MongoDB, you can currently configure the following options using the API:
- write concern
- read preference
- association storage strategy
- association document storage strategy
To set these options via the API, you need to create an OptionConfigurator
implementation as shown in the following example:
The call to configureOptionsFor()
, passing the store-specific identifier type MongoDB
, provides the entry point into the API. Following the fluent API pattern, you then can configure global options (writeConcern()
, readPreference()
) and navigate to single entities or properties to apply options specific to these (associationStorage()
etc.). The call to writeConcern()
for the Animal
entity shows how a specific write concern type can be used. Here RequiringReplicaCountOf
is a custom implementation of WriteConcern
which ensures that writes are propagated to a given number of replicas before a write is acknowledged.
Options given on the property level precede entity-level options. So e.g. the animals
association of the Zoo
class would be stored using the in entity strategy, while all other associations of the Zoo
entity would be stored using separate association documents.
Similarly, entity-level options take precedence over options given on the global level. Global-level options specified via the API complement the settings given via configuration properties. In case a setting is given via a configuration property and the API at the same time, the latter takes precedence.
Note that for a given level (property, entity, global), an option set via annotations is overridden by the same option set programmatically. This allows you to change settings in a more flexible way if required.
To register an option configurator, specify its class name using the hibernate.ogm.option.configurator
property. When bootstrapping a session factory or entity manager factory programmatically, you also can pass in an OptionConfigurator
instance or the class object representing the configurator type.
Hibernate OGM tries to make the mapping to the underlying datastore as natural as possible so that third party applications not using Hibernate OGM can still read and update the same datastore. We worked particularly hard on the MongoDB model to offer various classic mappings between your object model and the MongoDB documents.
To describe things simply, each entity is stored as a MongoDB document. This document is stored in a MongoDB collection named after the entity type. The navigational information for each association from one entity to (a set of) entity is stored in the document representing the entity we are departing from.
Entities are stored as MongoDB documents and not as BLOBs: each entity property will be translated into a document field. You can use @Table
and @Column
annotations to rename respectively the collection the document is stored in and the document’s field a property is persisted in.
Hibernate OGM stores elements annotated with @Embedded
or @ElementCollection
as nested documents of the owning entity.
The class GrandChild
has only one attribute name
, this means that Hibernate OGM doesn’t need to store the name of the attribute.
If the nested document has two or more fields, like in the following example, Hibernate OGM will store the name of the fields as well.
Note
You can override the column name used for a property of an embedded object. But you need to know that the default column name is the concatenation of the embedding property, a .
(dot) and the embedded property (recursively for several levels of embedded objects).
The MongoDB datastore treats dots specifically as it transforms them into nested documents. If you want to override one column name and still keep the nested structure, don’t forget the dots.
That’s a bit abstract, so let’s use an example.
@Entity
class Order {
@Id String number;
User user;
Address shipping;
@AttributeOverrides({
@AttributeOverride(name="name", column=@Column(name="delivery.provider"),
@AttributeOverride(name="expectedDelaysInDays", column=@Column(name="delivery.delays")
})
DeliveryProvider deliveryProvider;
CreditCardType cardType;
}
// default columns
@Embedded
class User {
String firstname;
String lastname;
}
// override one column
@Embeddable
public Address {
String street;
@Column(name="shipping.dest_city")
String city;
}
// both columns overridden from the embedding side
@Embeddable
public DeliveryProvider {
String name;
Integer expectedDelaysInDays;
}
// do not use dots in the overriding
// and mix levels (bad form)
@Embedded
class CreditCardType {
String merchant;
@Column(name="network")
String network;
}
{
"_id": "123RF33",
"user": {
"firstname": "Emmanuel",
"lastname": "Bernard"
},
"shipping": {
"street": "1 av des Champs Elysées",
"dest_city": "Paris"
},
"delivery": {
"provider": "Santa Claus Inc.",
"delays": "1"
}
"network": "VISA",
"cardType: {
"merchant": "Amazon"
}
}
If you share the same embeddable in different places, you can use JPA’s @AttributeOverride
to override columns from the embedding side. This is the case of DeliveryProvider
in our example.
If you omit the dot in one of the columns, this column will not be part of the nested document. This is demonstrated by the CreditCardType
. We advise you against it. Like crossing streams, it is bad form. This approach might not be supported in the future.
Hibernate OGM MongoDB proposes three strategies to store navigation information for associations. The three possible strategies are:
- IN_ENTITY (default)
- ASSOCIATION_DOCUMENT, using a global collection for all associations
- COLLECTION_PER_ASSOCIATION, using a dedicated collection for each association
To switch between these strategies, use of the three approaches to options:
annotate your entity with
@AssocationStorage
and@AssociationDocumentStorage
annotations (see Section 11.1.4, “Annotation based configuration”),use the API for programmatic configuration (see Section 11.1.5, “Programmatic configuration”)
or specify a default strategy via the
hibernate.ogm.datastore.document.association_storage
andhibernate.ogm.mongodb.association_document_storage
configuration properties.
In this strategy, Hibernate OGM stores the id(s) of the associated entity(ies) into the entity document itself. This field stores the id value for to-one associations and an array of id values for to-many associations. An embedded id will be represented by a nested document. For indexed collections (i.e. List
or Map
), the index will be stored along the id.
In a true one-to-one association, it is possible to share the same id between the two entities and therefore a foreign key is not required. You can see how to map this type of association in the following example:
Example 11.29. Bidirectional many-to-one between entities with embedded ids
@Entity
public class Game {
@EmbeddedId
private GameId id;
private String name;
@ManyToOne
private Court playedOn;
// getters, setters ...
}
public class GameId implements Serializable {
private String category;
@Column(name = "id.gameSequenceNo")
private int sequenceNo;
// getters, setters ...
// equals / hashCode
}
@Entity
public class Court {
@EmbeddedId
private CourtId id;
private String name;
@OneToMany(mappedBy = "playedOn")
private Set<Game> games = new HashSet<Game>();
// getters, setters ...
}
public class CourtId implements Serializable {
private String countryCode;
private int sequenceNo;
// getters, setters ...
// equals / hashCode
}
Court collection.
{
"_id" : {
"countryCode" : "DE",
"sequenceNo" : 123
},
"name" : "Hamburg Court",
"games" : [
{ "gameSequenceNo" : 457, "category" : "primary" },
{ "gameSequenceNo" : 456, "category" : "primary" }
]
}
Game collection.
{
"_id" : {
"category" : "primary",
"gameSequenceNo" : 456
},
"name" : "The game",
"playedOn_id" : {
"countryCode" : "DE",
"sequenceNo" : 123
}
}
{
"_id" : {
"category" : "primary",
"gameSequenceNo" : 457
},
"name" : "The other game",
"playedOn_id" : {
"countryCode" : "DE",
"sequenceNo" : 123
}
}
Here we see that the embedded id is represented as a nested document and directly referenced by the associations.
A map can be used to represents an association, in this case Hibernate OGM will store the key of the map and the associated id.
Example 11.32. Unidirectional one-to-many using maps with defaults
@Entity
public class User {
@Id
private String id;
@OneToMany
private Map<String, Address> addresses = new HashMap<String, Address>();
// getters, setters ...
}
@Entity
public class Address {
@Id
private String id;
private String city;
// getters, setters ...
}
User collection as JSON in MongoDB
{
"_id" : "user_001",
"addresses" : [
{
"addresses_KEY" : "work",
"addresses_id" : "address_001"
},
{
"addresses_KEY" : "home",
"addresses_id" : "address_002"
}
]
}
Address collection as JSON in MongoDB
{ "_id" : "address_001", "city" : "Rome" }
{ "_id" : "address_002", "city" : "Paris" }
You can use @MapKeyColumn to rename the column containing the key of the map.
Example 11.33. Unidirectional one-to-many using maps with @MapKeyColumn
@Entity
public class User {
@Id
private String id;
@OneToMany
@MapKeyColumn(name = "addressType")
private Map<String, Address> addresses = new HashMap<String, Address>();
// getters, setters ...
}
@Entity
public class Address {
@Id
private String id;
private String city;
// getters, setters ...
}
User collection as JSON in MongoDB
{
"_id" : "user_001",
"addresses" : [
{
"addressType" : "work",
"addresses_id" : "address_001"
},
{
"addressType" : "home",
"addresses_id" : "address_002"
}
]
}
Address collection as JSON in MongoDB
{ "_id" : "address_001", "city" : "Rome" }
{ "_id" : "address_002", "city" : "Paris" }
Example 11.36. Ordered list with embedded id
@Entity
public class Race {
@EmbeddedId
private RaceId raceId;
@OrderColumn(name = "ranking")
@OneToMany @JoinTable(name = "Race_Runners")
private List<Runner> runnersByArrival = new ArrayList<Runner>();
// getters, setters ...
}
public class RaceId implements Serializable {
private int federationSequence;
private int federationDepartment;
// getters, setters, equals, hashCode
}
@Entity
public class Runner {
@EmbeddedId
private RunnerId runnerId;
private int age;
// getters, setters ...
}
public class RunnerId implements Serializable {
private String firstname;
private String lastname;
// getters, setters, equals, hashCode
}
Race collection.
{
"_id": {
"federationDepartment": 75,
"federationSequence": 23
},
"runnersByArrival": [{
"firstname": "Pere",
"lastname": "Noel",
"ranking": 1
}, {
"firstname": "Emmanuel",
"lastname": "Bernard",
"ranking": 0
}]
}
Runner collection.
{
"_id": {
"firstname": "Pere",
"lastname": "Noel"
},
"age": 105
} {
"_id": {
"firstname": "Emmanuel",
"lastname": "Bernard"
},
"age": 37
}
In this strategy, Hibernate OGM creates a MongoDB collection per association in which it will store all navigation information for that particular association.
This is the strategy closest to the relational model. If an entity A is related to B and C, 2 collections will be created. The name of this collection is made of the association table concatenated with associations_
.
For example, if the BankAccount
and Owner
are related, the collection used to store will be named associations_Owner_BankAccount
. You can rename The prefix is useful to quickly identify the association collections from the entity collections. You can also decide to rename the collection representing the association using @JoinTable
(see an example)
Each document of an association collection has the following structure:
_id
contains the id of the owner of relationshiprows
contains all the id of the related entities
Note
The preferred approach is to use the in-entity strategybut this approach can alleviate the problem of having documents that are too big.
Note
This strategy won’t affect *-to-one associations or embedded collections.
The order of the element in the list might be preserved using @OrderColumn. Hibernate OGM will store the order adding an additional fieldd to the document containing the association.
Example 11.41. Unidirectional many-to-many using one collection per association strategy
@Entity
public class Student {
@Id
private String id;
private String name;
// getters, setters ...
}
@Entity
public class ClassRoom {
@Id
private long id;
private String lesson;
@ManyToMany
private List<Student> students = new ArrayList<Student>();
// getters, setters ...
}
Student collection
{
"_id" : "john",
"name" : "John Doe"
}
{
"_id" : "mario",
"name" : "Mario Rossi"
}
{
"_id" : "kate",
"name" : "Kate Doe"
}
ClassRoom collection
{
"_id" : NumberLong(1),
"lesson" : "Math"
}
{
"_id" : NumberLong(2),
"lesson" : "English"
}
associations_ClassRoom_Student
{
"_id" : {
"ClassRoom_id" : NumberLong(1),
},
"rows" : [ "john", "mario" ]
}
{
"_id" : {
"ClassRoom_id" : NumberLong(2),
},
"rows" : [ "mario", "kate" ]
}
You can change the name of the collection containing the association using the @JoinTable
annotation. In the following example, the name of the collection containing the association is OwnerBankAccounts
(instead of the default associations_AccountOwner_BankAccount
)
With this strategy, Hibernate OGM creates a single collection named Associations
in which it will store all navigation information for all associations. Each document of this collection is structured in 2 parts. The first is the _id
field which contains the identifier information of the association owner and the name of the association table. The second part is the rows
field which stores (into an embedded collection) all ids that the current instance is related to.
For a bidirectional relationship, another document is created where ids are reversed. Don’t worry, Hibernate OGM takes care of keeping them in sync:
Example 11.46. Unidirectional one-to-many using global collection strategy
@Entity
public class Basket {
@Id
private String id;
private String owner;
@OneToMany
private List<Product> products = new ArrayList<Product>();
// getters, setters ...
}
@Entity
public class Product {
@Id
private String name;
private String description;
// getters, setters ...
}
Basket collection
{
"_id" : "davide_basket",
"owner" : "Davide"
}
Product collection
{
"_id" : "Pretzel",
"description" : "Glutino Pretzel Sticks"
}
{
"_id" : "Beer",
"description" : "Tactical nuclear penguin"
}
Associations collection
{
"_id" : {
"Basket_id" : "davide_basket",
"table" : "Basket_Product"
},
"rows" : [
{
"products_name" : "Pretzel",
"products_ORDER" : 1
},
{
"products_name" : "Beer",
"products_ORDER" : 0
}
]
}
Example 11.48. Unidirectional many-to-many using global collection strategy
@Entity
public class Student {
@Id
private String id;
private String name;
// getters, setters ...
}
@Entity
public class ClassRoom {
@Id
private long id;
private String lesson;
@ManyToMany
private List<Student> students = new ArrayList<Student>();
// getters, setters ...
}
Student collection
{
"_id" : "john",
"name" : "John Doe"
}
{
"_id" : "mario",
"name" : "Mario Rossi"
}
{
"_id" : "kate",
"name" : "Kate Doe"
}
ClassRoom collection
{
"_id" : NumberLong(1),
"lesson" : "Math"
}
{
"_id" : NumberLong(2),
"lesson" : "English"
}
Associations collection
{
"_id" : {
"ClassRoom_id" : NumberLong(1),
"table" : "ClassRoom_Student"
},
"rows" : [ "john", "mario" ]
}
{
"_id" : {
"ClassRoom_id" : NumberLong(2),
"table" : "ClassRoom_Student"
},
"rows" : [ "mario", "kate" ]
}