Hibernate.orgCommunity Documentation
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.
We currently support version 3.3.0 and 3.2.
Configuring Hibernate OGM to use MongoDb is easy:
To add the dependencies via Maven, add the following module:
<dependency>
<groupId>org.hibernate.ogm</groupId>
<artifactId>hibernate-ogm-mongodb</artifactId>
<version>5.0.4.Final</version>
</dependency>
This will pull the MongoDB driver transparently.
If you’re not using a dependency management tool, copy all the dependencies from the distribution in the directories:
/lib/required
/lib/mongodb
/lib/provided
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
mongodb
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 is 127.0.0.1:27017
. If left undefined, the default port is 27017
.
hibernate.ogm.datastore.host
.
The port used by the MongoDB instance.
Ignored when multiple hosts are defined.
The default value is 27017
.ErrorHandler
to get notified upon errors during flushes (see Section 6.3.1, “Acting upon errors during application of changes”)String
, int
and boolean
properties
can be set, eg hibernate.ogm.mongodb.driver.serverSelectionTimeout
.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 RFCDefines 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 associationIN_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.
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 associationDefines the way OGM stores the contents of map-typed associations in MongoDB.
The following two strategies exist (values of the org.hibernate.ogm.datastore.document.options.MapStorageType
enum):
BY_KEY
: map-typed associations with a single key column which is of type String
will be stored as a sub-document,
organized by the given key; Not applicable for other types of key columns, in which case always AS_LIST
will be usedAS_LIST
: map-typed associations will be stored as an array containing a sub-document for each map entry.
All key and value columns will be contained within the array elementsDefines 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
.
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
.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 the official documentation.
When bootstrapping a session factory or entity manager factory programmatically,
you should use the constants accessible via org.hibernate.ogm.datastore.mongodb.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.
Fongo is an in-memory java implementation of MongoDB.
It intercepts calls to the standard mongo-java-driver for finds, updates, inserts, removes and other methods.
The primary use is for lightweight unit testing where you don’t want to spin up a mongod
process.
Hibernate OGM provides a FongoDB provider so during tests it can be used instead of MongoDB driver. Note that you don’t need to change your business code to adapt to FongoDB because all adaptations are done under the cover by Hibernate OGM.
To start using FongoDB provider, you should do two things:
The first one is register the provider by using hibernate.ogm.datastore.provider
and setting to fongodb
.
Example 11.1. Configuring FongoDB provider
<persistence-unit name="ogm-jpa-tutorial" transaction-type="JTA">
<provider>org.hibernate.ogm.jpa.HibernateOgmPersistence</provider>
<properties>
<property name="hibernate.ogm.datastore.provider" value="fongodb"/>
<property name="hibernate.transaction.jta.platform"
value="org.hibernate.service.jta.platform.internal.JBossStandAloneJtaPlatform"/>
</properties>
</persistence-unit>
The second one is adding FongoDB and SLF4J dependencies in your project.
<dependency>
<groupId>com.github.fakemongo</groupId>
<artifactId>fongo</artifactId>
<scope>test</scope>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.5</version>
<scope>test</scope>
</dependency>
You can read more about FongoDB project and its limitations at https://github.com/fakemongo/fongo
Hibernate OGM allows to configure store-specific options via Java annotations. You can override global configurations for a specific entity or even a specify property by virtue of the location where you place that annotation.
When working with the MongoDB backend, you can specify the following settings:
@WriteConcern
annotation@ReadPreference
annotation@AssociationStorage
and @AssociationDocumentStorage
annotations@MapStorage
annotationRefer to <<mongodb-associations> to learn more about the options related to storing associations.
The following shows an example:
Example 11.2. Configuring the association storage strategy using annotations
@Entity
@WriteConcern(WriteConcernType.JOURNALED)
@ReadPreference(ReadPreferenceType.PRIMARY_PREFERRED)
@AssociationStorage(AssociationStorageType.ASSOCIATION_DOCUMENT)
@AssociationDocumentStorage(AssociationDocumentStorageType.COLLECTION_PER_ASSOCIATION)
@MapStorage(MapStorageType.AS_LIST)
public class Zoo {
@OneToMany
private Set<Animal> animals;
@OneToMany
private Set<Person> employees;
@OneToMany
@AssociationStorage(AssociationStorageType.IN_ENTITY)
private Set<Person> visitors;
// getters, setters ...
}
The @WriteConcern
annotation on the entity level expresses that all writes should be done using the JOURNALED
setting.
Similarly, the @ReadPreference
annotation advices the engine to preferably read that entity from the primary node if possible.
The other two annotations on the type-level specify that all associations of the Zoo
class should be stored in separate assocation documents, using a dedicated collection per association.
This setting applies to the animals
and employees
associations.
Only the elements of the visitors
association will be stored in the document of the corresponding Zoo
entity
as per the configuration of that specific property which takes precedence over the entity-level configuration.
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:
To set these options via the API, you need to create an OptionConfigurator
implementation
as shown in the following example:
Example 11.3. Example of an option configurator
public class MyOptionConfigurator extends OptionConfigurator {
@Override
public void configure(Configurable configurable) {
configurable.configureOptionsFor( MongoDB.class )
.writeConcern( WriteConcernType.REPLICA_ACKNOWLEDGED )
.readPreference( ReadPreferenceType.NEAREST )
.entity( Zoo.class )
.associationStorage( AssociationStorageType.ASSOCIATION_DOCUMENT )
.associationDocumentStorage( AssociationDocumentStorageType.COLLECTION_PER_ASSOCIATION )
.mapStorage( MapStorageType.ASLIST )
.property( "animals", ElementType.FIELD )
.associationStorage( AssociationStorageType.IN_ENTITY )
.entity( Animal.class )
.writeConcern( new RequiringReplicaCountOf( 3 ) )
.associationStorage( AssociationStorageType.ASSOCIATION_DOCUMENT );
}
}
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.
Each entity is represented by a document. Each property or more precisely column is represented by a field in this document, the field name being the column name.
Hibernate OGM supports by default the following property types:
java.lang.String
{ "text" : "Hello world!" }
java.lang.Character
(or char primitive) { "delimiter" : "/" }
java.lang.Boolean
(or boolean primitive) { "favorite" : true } # default mapping
{ "favorite" : "T" } # if @Type(type = "true_false") is given
{ "favorite" : "Y" } # if @Type(type = "yes_no") is given
{ "favorite" : 1 } # if @Type(type = "numeric_boolean") is given
java.lang.Byte
(or byte primitive) { "display_mask" : "70" }
java.lang.Byte[]
(or byte[]) { "pdfAsBytes" : BinData(0,"MTIzNDU=") }
java.lang.Short
(or short primitive) { "urlPort" : 80 }
java.lang.Integer
(or integer primitive) { "stockCount" : 12309 }
java.lang.Long
(or long primitive) { "userId" : NumberLong("-6718902786625749549") }
java.lang.Float
(or float primitive) { "visitRatio" : 10.39 }
java.lang.Double
(or double primitive) { "tax_percentage" : 12.34 }
java.math.BigDecimal
{ "site_weight" : "21.77" }
java.math.BigInteger
{ "site_weight" : "444" }
java.util.Calendar
{ "creation" : "2014/11/03 16:19:49:283 +0000" }
java.util.Date
{ "last_update" : ISODate("2014-11-03T16:19:49.283Z") }
java.util.UUID
{ "serialNumber" : "71f5713d-69c4-4b62-ad15-aed8ce8d10e0" }
java.util.URL
{ "url" : "http://www.hibernate.org/" }
org.bson.types.ObjectId
{ "object_id" : ObjectId("547d9b40e62048750f25ef77") }
Hibernate OGM doesn’t store null values in MongoDB, setting a value to null is the same as removing the field in the corresponding object in the db.
This can have consequences when it comes to queries on null value.
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.
Example 11.4. Default JPA mapping for an entity
@Entity
public class News {
@Id
private String id;
private String title;
// getters, setters ...
}
// Stored in the Collection "News"
{
"_id" : "1234-5678-0123-4567",
"title": "On the merits of NoSQL",
}
Example 11.5. Rename field and collection using @Table and @Column
@Entity
// Overrides the collection name
@Table(name = "News_Collection")
public class News {
@Id
private String id;
// Overrides the field name
@Column(name = "headline")
private String title;
// getters, setters ...
}
// Stored in the Collection "News"
{
"_id" : "1234-5678-0123-4567",
"headline": "On the merits of NoSQL",
}
Hibernate OGM always store identifiers using the _id
field of a MongoDB document ignoring
the name of the property in the entity.
That’s a good thing as MongoDB has special treatment and expectation of the property _id
.
An identifier type may be one of the built-in types
or a more complex type represented by an embedded class.
When you use a built-in type, the identifier is mapped like a regular property.
When you use an embedded class, then the _id
is representing a nested document
containing the embedded class properties.
Example 11.6. Define an identifier as a primitive type
@Entity
public class Bookmark {
@Id
private String id;
private String title;
// getters, setters ...
}
{
"_id" : "bookmark_1"
"title" : "Hibernate OGM documentation"
}
Example 11.7. Define an identifier using @EmbeddedId
@Embeddable
public class NewsID implements Serializable {
private String title;
private String author;
// getters, setters ...
}
@Entity
public class News {
@EmbeddedId
private NewsID newsId;
private String content;
// getters, setters ...
}
News collection as JSON in MongoDB
{
"_id" : {
"author" : "Guillaume",
"title" : "How to use Hibernate OGM ?"
},
"content" : "Simple, just like ORM but with a NoSQL database"
}
Generally, it is recommended though to work with MongoDB’s object id data type. This will facilitate the integration with other applications expecting that common MongoDB id type. To do so, you have two options:
org.bson.types.ObjectId
String
and annotate it with @Type(type="objectid")
In both cases the id will be stored as native ObjectId
in the datastore.
Example 11.8. Define an id as ObjectId
@Entity
public class News {
@Id
private ObjectId id;
private String title;
// getters, setters ...
}
Example 11.9. Define an id of type String as ObjectId
@Entity
public class News {
@Id
@Type(type = "objectid")
private String id;
private String title;
// getters, setters ...
}
You can assign id values yourself or let Hibernate OGM generate the value using the
@GeneratedValue
annotation.
There are 4 different strategies:
1) IDENTITY generation strategy
The preferable strategy, Hibernate OGM will create the identifier upon insertion. To apply this strategy the id must be one of the following:
@Type(type="objectid")
org.bson.types.ObjectId
like in the following examples:
Example 11.10. Define an id of type String as ObjectId
@Entity
public class News {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Type(type = "objectid")
private String id;
private String title;
// getters, setters ...
}
{
"_id" : ObjectId("5425448830048b67064d40b1"),
"title" : "Exciting News"
}
Example 11.11. Define an id as ObjectId
@Entity
public class News {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private ObjectId id;
private String title;
// getters, setters ...
}
{
"_id" : ObjectId("5425448830048b67064d40b1"),
"title" : "Exciting News"
}
Example 11.12. Id generation strategy TABLE using default values
@Entity
public class GuitarPlayer {
@Id
@GeneratedValue(strategy = GenerationType.TABLE)
private Long id;
private String name;
// getters, setters ...
}
GuitarPlayer collection
{
"_id" : NumberLong(1),
"name" : "Buck Cherry"
}
hibernate_sequences collection
{
"_id" : "GuitarPlayer",
"next_val" : 101
}
Example 11.13. Id generation strategy TABLE using a custom table
@Entity
public class GuitarPlayer {
@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "guitarGen")
@TableGenerator(
name = "guitarGen",
table = "GuitarPlayerSequence",
pkColumnValue = "guitarPlayer",
valueColumnName = "nextGuitarPlayerId"
)
private long id;
// getters, setters ...
}
GuitarPlayer collection
{
"_id" : NumberLong(1),
"name" : "Buck Cherry"
}
GuitarPlayerSequence collection
{
"_id" : "guitarPlayer",
"nextGuitarPlayerId" : 2
}
3) SEQUENCE generation strategy
Example 11.14. SEQUENCE id generation strategy using default values
@Entity
public class Song {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
private String title;
// getters, setters ...
}
Song collection
{
"_id" : NumberLong(2),
"title" : "Flower Duet"
}
hibernate_sequences collection
{ "_id" : "song_sequence_name", "next_val" : 21 }
Example 11.15. SEQUENCE id generation strategy using custom values
@Entity
public class Song {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "songSequenceGenerator")
@SequenceGenerator(
name = "songSequenceGenerator",
sequenceName = "song_seq",
initialValue = 2,
allocationSize = 20
)
private Long id;
private String title;
// getters, setters ...
}
Song collection
{
"_id" : NumberLong(2),
"title" : "Flower Duet"
}
hibernate_sequences collection
{ "_id" : "song_seq", "next_val" : 42 }
Care must be taken when using the GenerationType.AUTO
strategy.
When the property hibernate.id.new_generator_mappings
is set to false
(default),
it will map to the IDENTITY
strategy.
As described before, this requires your ids to be of type ObjectId
or @Type(type = "objectid") String
.
If hibernate.id.new_generator_mappings
is set to true, AUTO
will be mapped to the TABLE
strategy.
This requires your id to be of a numeric type.
We recommend to not use AUTO
but one of the explicit strategies (IDENTITY
or TABLE
) to avoid
potential misconfigurations.
For more details you can check the issue OGM-663.
If the property hibernate.id.new_generator_mappings
is set to false
,
AUTO
will behave as the IDENTITY
strategy.
If the property hibernate.id.new_generator_mappings
is set to true
,
AUTO
will behave as the SEQUENCE
strategy.
Example 11.16. AUTO id generation strategy using default values
@Entity
public class DistributedRevisionControl {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
// getters, setters ...
}
DistributedRevisionControl collection
{ "_id" : NumberLong(1), "name" : "Git" }
hibernate_sequences collection
{ "_id" : "hibernate_sequence", "next_val" : 2 }
Example 11.17. AUTO id generation strategy wih hibernate.id.new_generator_mappings
set to false and ObjectId
@Entity
public class Comedian {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private ObjectId id;
private String name;
// getters, setters ...
}
Comedian collection
{ "_id" : ObjectId("5458b11693f4add0f90519c5"), "name" : "Louis C.K." }
Example 11.18. Entity with @EmbeddedId
@Entity
public class News {
@EmbeddedId
private NewsID newsId;
// getters, setters ...
}
@Embeddable
public class NewsID implements Serializable {
private String title;
private String author;
// getters, setters ...
}
Rendered as JSON in MongoDB
{
"_id" :{
"title": "How does Hibernate OGM MongoDB work?",
"author": "Guillaume"
}
}
Hibernate OGM stores elements annotated with @Embedded
or @ElementCollection
as nested documents of the owning entity.
Example 11.19. Embedded object
@Entity
public class News {
@Id
private String id;
private String title;
@Embedded
private NewsPaper paper;
// getters, setters ...
}
@Embeddable
public class NewsPaper {
private String name;
private String owner;
// getters, setters ...
}
{
"_id" : "1234-5678-0123-4567",
"title": "On the merits of NoSQL",
"paper": {
"name": "NoSQL journal of prophecies",
"owner": "Delphy"
}
}
Example 11.20. @ElementCollection with primitive types
@Entity
public class AccountWithPhone {
@Id
private String id;
@ElementCollection
private List<String> mobileNumbers;
// getters, setters ...
}
AccountWithPhone collection
{
"_id" : "john_account",
"mobileNumbers" : [ "+1-222-555-0222", "+1-202-555-0333" ]
}
Example 11.21. @ElementCollection with one attribute
@Entity
public class GrandMother {
@Id
private String id;
@ElementCollection
private List<GrandChild> grandChildren = new ArrayList<GrandChild>();
// getters, setters ...
}
@Embeddable
public class GrandChild {
private String name;
// getters, setters ...
}
{
"_id" : "df153180-c6b3-4a4c-a7da-d5de47cf6f00",
"grandChildren" : [ "Luke", "Leia" ]
}
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.
Example 11.22. @ElementCollection with @OrderColumn
@Entity
public class GrandMother {
@Id
private String id;
@ElementCollection
@OrderColumn( name = "birth_order" )
private List<GrandChild> grandChildren = new ArrayList<GrandChild>();
// getters, setters ...
}
@Embeddable
public class GrandChild {
private String name;
// getters, setters ...
}
{
"_id" : "e3e1ed4e-c685-4c3f-9a67-a5aeec6ff3ba",
"grandChildren" :
[
{
"name" : "Luke",
"birth_order" : 0
},
{
"name" : "Leia",
"birthorder" : 1
}
]
}
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:
To switch between these strategies, use of the three approaches to options:
@AssocationStorage
and @AssociationDocumentStorage
annotations (see Section 11.1.4, “Annotation based configuration”),hibernate.ogm.datastore.document.association_storage
and
hibernate.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.
When using this strategy the annotations @JoinTable
will be ignored because no collection is created
for associations.
You can use @JoinColumn
to change the name of the field that stores the foreign key (as an example, see
Example 11.24, “Unidirectional one-to-one with @JoinColumn”).
Example 11.23. Unidirectional one-to-one
@Entity
public class Vehicule {
@Id
private String id;
private String brand;
// getters, setters ...
}
@Entity
public class Wheel {
@Id
private String id;
private double diameter;
@OneToOne
private Vehicule vehicule;
// getters, setters ...
}
{
"_id" : "V_01",
"brand" : "Mercedes"
}
Wheel collection as JSON in MongoDB
{
"_id" : "W001",
"diameter" : 0,
"vehicule_id" : "V_01"
}
Example 11.24. Unidirectional one-to-one with @JoinColumn
@Entity
public class Vehicule {
@Id
private String id;
private String brand;
// getters, setters ...
}
@Entity
public class Wheel {
@Id
private String id;
private double diameter;
@OneToOne
@JoinColumn( name = "part_of" )
private Vehicule vehicule;
// getters, setters ...
}
{
"_id" : "V_01",
"brand" : "Mercedes"
}
Wheel collection as JSON in MongoDB
{
"_id" : "W001",
"diameter" : 0,
"part_of" : "V_01"
}
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.25. Unidirectional one-to-one with @MapsId and @PrimaryKeyJoinColumn
@Entity
public class Vehicule {
@Id
private String id;
private String brand;
// getters, setters ...
}
@Entity
public class Wheel {
@Id
private String id;
private double diameter;
@OneToOne
@PrimaryKeyJoinColumn
@MapsId
private Vehicule vehicule;
// getters, setters ...
}
Vehicule collection as JSON in MongoDB
{
"_id" : "V_01",
"brand" : "Mercedes"
}
Wheel collection as JSON in MongoDB
{
"_id" : "V_01",
"diameter" : 0,
}
Example 11.26. Bidirectional one-to-one
@Entity
public class Husband {
@Id
private String id;
private String name;
@OneToOne
private Wife wife;
// getters, setters ...
}
@Entity
public class Wife {
@Id
private String id;
private String name;
@OneToOne
private Husband husband;
// getters, setters ...
}
Husband collection as JSON in MongoDB
{
"_id" : "alex",
"name" : "Alex",
"wife" : "bea"
}
Wife collection as JSON in MongoDB
{
"_id" : "bea",
"name" : "Bea",
"husband" : "alex"
}
Example 11.27. Unidirectional many-to-one
@Entity
public class JavaUserGroup {
@Id
private String jugId;
private String name;
// getters, setters ...
}
@Entity
public class Member {
@Id
private String id;
private String name;
@ManyToOne
private JavaUserGroup memberOf;
// getters, setters ...
}
JavaUserGroup collection as JSON in MongoDB
{
"_id" : "summer_camp",
"name" : "JUG Summer Camp"
}
Member collection as JSON in MongoDB
{
"_id" : "jerome",
"name" : "Jerome"
"memberOf_jugId" : "summer_camp"
}
{
"_id" : "emmanuel",
"name" : "Emmanuel Bernard"
"memberOf_jugId" : "summer_camp"
}
Example 11.28. Bidirectional many-to-one
@Entity
public class SalesForce {
@Id
private String id;
private String corporation;
@OneToMany(mappedBy = "salesForce")
private Set<SalesGuy> salesGuys = new HashSet<SalesGuy>();
// getters, setters ...
}
@Entity
public class SalesGuy {
private String id;
private String name;
@ManyToOne
private SalesForce salesForce;
// getters, setters ...
}
SalesForce collection
{
"_id" : "red_hat",
"corporation" : "Red Hat",
"salesGuys" : [ "eric", "simon" ]
}
SalesGuy collection
{
"_id" : "eric",
"name" : "Eric"
"salesForce_id" : "red_hat",
}
{
"_id" : "simon",
"name" : "Simon",
"salesForce_id" : "red_hat"
}
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.
Example 11.30. Unidirectional one-to-many
@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",
"products" : [ "Beer", "Pretzel" ]
}
Product collection
{
"_id" : "Pretzel",
"description" : "Glutino Pretzel Sticks"
}
{
"_id" : "Beer",
"description" : "Tactical nuclear penguin"
}
Example 11.31. Unidirectional one-to-many with @OrderColumn
@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",
"products" : [
{
"products_name" : "Pretzel",
"products_ORDER" : 1
},
{
"products_name" : "Beer",
"products_ORDER" : 0
}
]
}
Product collection
{
"_id" : "Pretzel",
"description" : "Glutino Pretzel Sticks"
}
{
"_id" : "Beer",
"description" : "Tactical nuclear penguin"
}
A map can be used to represent 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" : [
{
"work" : "address_001",
"home" : "address_002"
}
]
}
Address collection as JSON in MongoDB
{ "_id" : "address_001", "city" : "Rome" }
{ "_id" : "address_002", "city" : "Paris" }
If the map value cannot be represented by a single field (e.g. when referencing a type with a composite id or using an embeddable type as map value type), a sub-document containing all the required fields will be stored as value.
If the map key either is not of type String
or it is made up of several columns (composite map key),
the optimized structure shown in the example above cannot be used as MongoDB only allows for Strings as field names.
In that case the association will be represented by a list of sub-documents, also containing the map key column(s).
You can use @MapKeyColumn
to rename the field containing the key of the map,
otherwise it will default to "<%COLLECTION_ROLE%>_KEY", e.g. "addresses_KEY".
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<Long, Address> addresses = new HashMap<Long, 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" : 1,
"addresses_id" : "address_001"
},
{
"addressType" : 2,
"addresses_id" : "address_002"
}
]
}
Address collection as JSON in MongoDB
{ "_id" : "address_001", "city" : "Rome" }
{ "_id" : "address_002", "city" : "Paris" }
In case you want to enforce the list-style represention also for maps with a single key column of type String
(e.g. when reading back data persisted by earlier versions of Hibernate OGM),
you can do so by setting the option hibernate.ogm.datastore.document.map_storage
to the value AS_LIST
.
Example 11.34. Unidirectional many-to-many using in entity 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"
"students" : [
"mario",
"john"
]
}
{
"_id" : NumberLong(2),
"lesson" : "English"
"students" : [
"mario",
"kate"
]
}
Example 11.35. Bidirectional many-to-many
@Entity
public class AccountOwner {
@Id
private String id;
private String SSN;
@ManyToMany
private Set<BankAccount> bankAccounts;
// getters, setters ...
}
@Entity
public class BankAccount {
@Id
private String id;
private String accountNumber;
@ManyToMany( mappedBy = "bankAccounts" )
private Set<AccountOwner> owners = new HashSet<AccountOwner>();
// getters, setters ...
}
AccountOwner collection
{
"_id" : "owner_1",
"SSN" : "0123456"
"bankAccounts" : [ "account_1" ]
}
BankAccount collection
{
"_id" : "account_1",
"accountNumber" : "X2345000"
"owners" : [ "owner_1", "owner2222" ]
}
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 entitiesThe preferred approach is to use the in-entity strategy but this approach can alleviate the problem of having documents that are too big.
Example 11.37. Unidirectional relationship
{
"_id" : { "owners_id" : "owner0001" },
"rows" : [
"accountABC",
"accountXYZ"
]
}
Example 11.38. Bidirectional relationship
{
"_id" : { "owners_id" : "owner0001" },
"rows" : [ "accountABC", "accountXYZ" ]
}
{
"_id" : { "bankAccounts_id" : "accountXYZ" },
"rows" : [ "owner0001" ]
}
This strategy won’t affect *-to-one associations or embedded collections.
Example 11.39. Unidirectional one-to-many using one collection per 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_Basket_Product collection
{
"_id" : { "Basket_id" : "davide_basket" },
"rows" : [ "Beer", "Pretzel" ]
}
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.40. Unidirectional one-to-many using one collection per strategy with @OrderColumn
@Entity
public class Basket {
@Id
private String id;
private String owner;
@OneToMany
@OrderColumn
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_Basket_Product collection
{
"_id" : { "Basket_id" : "davide_basket" },
"rows" : [
{
"products_name" : "Pretzel",
"products_ORDER" : 1
},
{
"products_name" : "Beer",
"products_ORDER" : 0
}
]
}
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" ]
}
Example 11.42. Bidirectional many-to-many using one collection per association strategy
@Entity
public class AccountOwner {
@Id
private String id;
private String SSN;
@ManyToMany
private Set<BankAccount> bankAccounts;
// getters, setters ...
}
@Entity
public class BankAccount {
@Id
private String id;
private String accountNumber;
@ManyToMany(mappedBy = "bankAccounts")
private Set<AccountOwner> owners = new HashSet<AccountOwner>();
// getters, setters ...
}
AccountOwner collection
{
"_id" : "owner_1",
"SSN" : "0123456"
}
BankAccount collection
{
"_id" : "account_1",
"accountNumber" : "X2345000"
}
associations_AccountOwner_BankAccount collection
{
"_id" : {
"bankAccounts_id" : "account_1"
},
"rows" : [ "owner_1" ]
}
{
"_id" : {
"owners_id" : "owner_1"
},
"rows" : [ "account_1" ]
}
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
)
Example 11.43. Bidirectional many-to-many using one collection per association strategy and @JoinTable
@Entity
public class AccountOwner {
@Id
private String id;
private String SSN;
@ManyToMany
@JoinTable( name = "OwnerBankAccounts" )
private Set<BankAccount> bankAccounts;
// getters, setters ...
}
@Entity
public class BankAccount {
@Id
private String id;
private String accountNumber;
@ManyToMany(mappedBy = "bankAccounts")
private Set<AccountOwner> owners = new HashSet<AccountOwner>();
// getters, setters ...
}
AccountOwner collection
{
"_id" : "owner_1",
"SSN" : "0123456"
}
BankAccount collection
{
"_id" : "account_1",
"accountNumber" : "X2345000"
}
OwnerBankAccount
{
"_id" : {
"bankAccounts_id" : "account_1"
},
"rows" : [ "owner_1" ]
}
{
"_id" : {
"owners_id" : "owner_1"
},
"rows" : [ "account_1" ]
}
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.
This strategy won’t affect *-to-one associations or embedded collections.
Generally, you should not make use of this strategy unless embedding the association information proves to be too big for your document and you wish to separate them.
Example 11.44. Associations collection containing unidirectional association
{
"_id": {
"owners_id": "owner0001",
"table": "AccountOwner_BankAccount"
},
"rows": [ "accountABC", "accountXYZ" ]
}
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.45. Associations collection containing a bidirectional association
{
"_id": {
"owners_id": "owner0001",
"table": "AccountOwner_BankAccount"
},
"rows": [ "accountABC", "accountXYZ" ]
}
{
"_id": {
"bankAccounts_id": "accountXYZ",
"table": "AccountOwner_BankAccount"
},
"rows": [ "owner0001" ]
}
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.47. Unidirectional one-to-many using global collection strategy with @JoinTable
@Entity
public class Basket {
@Id
private String id;
private String owner;
@OneToMany
// It will change the value stored in the field table in the Associations collection
@JoinTable( name = "BasketContent" )
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" : "BasketContent"
},
"rows" : [ "Beer", "Pretzel" ]
}
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" ]
}
Example 11.49. Bidirectional many-to-many using global collection strategy
@Entity
public class AccountOwner {
@Id
private String id;
private String SSN;
@ManyToMany
private Set<BankAccount> bankAccounts;
// getters, setters ...
}
@Entity
public class BankAccount {
@Id
private String id;
private String accountNumber;
@ManyToMany(mappedBy = "bankAccounts")
private Set<AccountOwner> owners = new HashSet<AccountOwner>();
// getters, setters ...
}
AccountOwner collection
{
"_id" : "owner0001",
"SSN" : "0123456"
}
BankAccount collection
{
"_id" : "account_1",
"accountNumber" : "X2345000"
}
Associations collection
{
"_id" : {
"bankAccounts_id" : "account_1",
"table" : "AccountOwner_BankAccount"
},
"rows" : [ "owner0001" ]
}
{
"_id" : {
"owners_id" : "owner0001",
"table" : "AccountOwner_BankAccount"
},
"rows" : [ "account_1" ]
}
MongoDB does not support transactions. Only changes applied to the same document are done atomically. A change applied to more than one document will not be applied atomically. This problem is slightly mitigated by the fact that Hibernate OGM queues all changes before applying them during flush time. So the window of time used to write to MongoDB is smaller than what you would have done manually.
We recommend that you still use transaction demarcations with Hibernate OGM to trigger the flush operation transparently (on commit). But do not consider rollback as a possibility, this won’t work.
MongoDB does not provide a built-in mechanism for detecting concurrent updates to the same document but it provides a way to execute atomic find and update operations. By exploiting this commands Hibernate OGM can detect concurrent modifications to the same document.
You can enable optimistic locking detection using the annotation @Version
:
Example 11.50. Optimistic locking detection via @Version
@Entity
public class Planet implements Nameable {
@Id
private String id;
private String name;
@Version
private int version;
// getters, setters ...
}
{ "_id" : "planet-1", "name" : "Pluto", "version" : 0 }
The @Version
annotation define which attribute will keep track of the version of the document,
Hibernate OGM will update the field when required and if two changes from two different sessions (for example)
are applied to the same document a org.hibernate.StaleObjectStateException
is thrown.
You can use @Column
to change the name of the field created on MongoDB:
Example 11.51. Optimistic locking detection via @Version
using @Column
@Entity
public class Planet implements Nameable {
@Id
private String id;
private String name;
@Version
@Column(name="OPTLOCK")
private int version;
// getters, setters ...
}
{ "_id" : "planet-1", "name" : "Pluto", "OPTLOCK" : 0 }
You can express queries in a few different ways:
While you can use JP-QL for simple queries, you might hit limitations. The current recommended approach is to use native MongoQL if your query involves nested (list of) elements.
In order to reflect changes performed in the current session, all entities affected by a given query are flushed to the datastore prior to query execution (that’s the case for Hibernate ORM as well as Hibernate OGM).
For not fully transactional stores such as MongoDB this can cause changes to be written as a side-effect of running queries which cannot be reverted by a possible later rollback.
Depending on your specific use cases and requirements you may prefer to disable auto-flushing,
e.g. by invoking query.setFlushMode( FlushMode.MANUAL )
.
Bear in mind though that query results will then not reflect changes applied within the current session.
Hibernate OGM is a work in progress, so only a sub-set of JP-QL constructs is available when using the JP-QL query support. This includes:
IS NULL
and IS NOT NULL
AND
, OR
, NOT
LIKE
, IN
and BETWEEN
ORDER BY
JOIN
on embedded collectionsQueries using these constructs will be transformed into equivalent native MongoDB queries.
Let us know by opening an issue or sending an email what query you wish to execute. Expanding our support in this area is high on our priority list.
Hibernate OGM also supports certain forms of native queries for MongoDB. Currently two forms of native queries are available via the MongoDB backend:
The former always maps results to entity types.
The latter either maps results to entity types or to certain supported forms of projection.
Note that parameterized queries are not supported by MongoDB, so don’t expect Query#setParameter()
to work.
You can execute native queries as shown in the following example:
Example 11.52. Using the JPA API
@Entity
public class Poem {
@Id
private Long id;
private String name;
private String author;
// getters, setters ...
}
...
javax.persistence.EntityManager em = ...
// criteria-only find syntax
String query1 = "{ $and: [ { name : 'Portia' }, { author : 'Oscar Wilde' } ] }";
Poem poem = (Poem) em.createNativeQuery( query1, Poem.class ).getSingleResult();
// criteria-only find syntax with order-by
String query2 = "{ $query : { author : 'Oscar Wilde' }, $orderby : { name : 1 } }";
List<Poem> poems = em.createNativeQuery( query2, Poem.class ).getResultList();
// projection via CLI-syntax
String query3 = "db.WILDE_POEM.find(" +
"{ '$query' : { 'name' : 'Athanasia' }, '$orderby' : { 'name' : 1 } }" +
"{ 'name' : 1 }" +
")";
// will contain name and id as MongoDB always returns the id for projections
List<Object[]> poemNames = (List<Object[]>)em.createNativeQuery( query3 ).getResultList();
// projection via CLI-syntax
String query4 = "db.WILDE_POEM.count({ 'name' : 'Athanasia' })";
Object[] count = (Object[])em.createNativeQuery( query4 ).getSingleResult();
The result of a query is a managed entity (or a list thereof) or a projection of attributes in form of an object array, just like you would get from a JP-QL query.
Example 11.53. Using the Hibernate native API
OgmSession session = ...
String query1 = "{ $and: [ { name : 'Portia' }, { author : 'Oscar Wilde' } ] }";
Poem poem = session.createNativeQuery( query1 )
.addEntity( "Poem", Poem.class )
.uniqueResult();
String query2 = "{ $query : { author : 'Oscar Wilde' }, $orderby : { name : 1 } }";
List<Poem> poems = session.createNativeQuery( query2 )
.addEntity( "Poem", Poem.class )
.list();
Native queries can also be created using the @NamedNativeQuery
annotation:
Example 11.54. Using @NamedNativeQuery
@Entity
@NamedNativeQuery(
name = "AthanasiaPoem",
query = "{ $and: [ { name : 'Athanasia' }, { author : 'Oscar Wilde' } ] }",
resultClass = Poem.class )
public class Poem { ... }
...
// Using the EntityManager
Poem poem1 = (Poem) em.createNamedQuery( "AthanasiaPoem" )
.getSingleResult();
// Using the Session
Poem poem2 = (Poem) session.getNamedQuery( "AthanasiaPoem" )
.uniqueResult();
Hibernate OGM stores data in a natural way so you can still execute queries using the MongoDB driver, the main drawback is that the results are going to be raw MongoDB documents and not managed entities.
Specifying native MongoDB queries using the CLI syntax is an EXPERIMENTAL feature for the time being.
Hibernate OGM can execute native queries expressed using the MongoDB CLI syntax with some limitations.
Currently find()
, findOne()
, findAndModify()
, and count()
queries are supported. Furthermore, three
types of write queries are supported via the CLI syntax: insert()
, remove()
, and update()
. Other query
types may be supported in future versions.
As one would expect, find()
, findOne()
, findAndModify()
, and count()
can be executed using
javax.persistence.Query.getSingleResult()
or javax.persistence.Query.getResultList()
, while insert()
,
remove()
, and update()
require using javax.persistence.Query.executeUpdate()
. Also note that,
javax.persistence.Query.executeUpdate()
may return -1
in case execution of a query was not acknowledged
relative to the write concern used.
The following functions can be used in the provided JSON:
BinData
, Date
, HexData
, ISODate
, NumberLong
, ObjectId
, Timestamp
,
RegExp
, DBPointer
, UUID
, GUID
, CSUUID
, CSGUID
, JUUID
, JGUID
, PYUUID
, PYGUID
.
NumberInt
is not supported as it is currently not supported by the MongoDB Java driver.
No cursor operations such as sort()
are supported.
Instead use the corresponding MongoDB query modifiers
such as $orderby
within the criteria parameter.
You can limit the results of a query using the setMaxResults(…)
method.
JSON parameters passed via the CLI syntax must be specified using the strict mode. Specifically, keys need to be given within quotes; the only relaxation of this is that single quotes may be used when specifying attribute names/values to facilitate embedding queries within Java strings.
Note that results of projections are returned as retrieved from the MongoDB driver at the moment and are not (yet) converted using suitable Hibernate OGM type implementations. This requirement is tracked under OGM-1031.
Example 11.55. CLI syntax examples
// Valid syntax
String valid = "db.Poem.find({ \"name\" : \"Athanasia\" })";
String alsoValid = "db.Poem.find({ '$or' : [{'name': 'Athanasia' }, {'name': 'Portia' }]})";
// NOT Valid syntax, it will throw an exception: com.mongodb.util.JSONParseException
String notValid = "db.Poem.find({ name : \"Athanasia\" })";
String alsoNotValid = "db.Poem.find({ $or : [{name: 'Athanasia' }, {name: 'Portia' }]})";
Example 11.56. CLI syntax sort and limit results alternatives
String nativeQuery = "db.Poem.find({ '$query': { 'author': 'Oscar Wilde' }, '$orderby' : { 'name' : 1 } })";
// Using hibernate session
List<Poem> result = session.createNativeQuery( nativeQuery )
.addEntity( Poem.class )
.setMaxResults( 2 )
.list();
// Using JPA entity manager
List<Poem> results = em.createNativeQuery( nativeQuery, Poem.class )
.setMaxResults( 2 )
.getResultList();
Example 11.57. CLI syntax update examples
String updateQuery = "db.Poem.findAndModify({ 'query': {'_id': 1}, 'update': { '$set': { 'author': 'Oscar Wilde' } }, 'new': true })";
List<Poem> updated = session.createNativeQuery( updateQuery ).addEntity( Poem.class ).list();
String insertQuery = "db.Poem.insert({ '_id': { '$numberLong': '11' }, 'author': 'Oscar Wilder', 'name': 'The one and wildest', 'rating': '1' } )";
int inserted = session.createNativeQuery( insertQuery ).executeUpdate();
String removeQuery = "db.Poem.remove({ '_id': { '$numberLong': '11' } })";
int removed = session.createNativeQuery( removeQuery ).executeUpdate();
Support for the $regexp
operator is limited to the string syntax. We do not support the /pattern/
syntax as it is not
currently supported by the MongoDB Java driver.
You can index your entities using Hibernate Search. That way, a set of secondary indexes independent of MongoDB is maintained by Hibernate Search and you can run Lucene queries on top of them. The benefit of this approach is a nice integration at the JPA / Hibernate API level (managed entities are returned by the queries). The drawback is that you need to store the Lucene indexes somewhere (file system, infinispan grid, etc). Have a look at the Infinispan section (Section 9.6, “Storing a Lucene index in Infinispan”) for more info on how to use Hibernate Search.