Help

I'm member of the Hibernate Team at Red Hat, currently working on Hibernate Validator, the reference implementation of JSR 349 (Bean Validation). I'm also serving in the JSR 349 expert group.

Location: Hamburg, Germany
Occupation: Hibernate team member
Archive

I am pleased to announce the release of Hibernate OGM 4.2.0.CR1.

The new version comes to you with initial support for Apache Cassandra, several improvements around JP-QL queries, support for MongoDB 3, MongoDB replica sets, new built-in types and much more.

You can download Hibernate OGM 4.2.0.CR1 from SourceForge or via Maven, Gradle etc., using the following GAV coordinates:

  • org.hibernate.ogm:hibernate-ogm-core:4.2.0.CR1 for the Hibernate OGM engine and
  • org.hibernate.ogm:hibernate-ogm-<%DATASTORE%>:4.2.0.CR1, depending on the backend you want to use.

Preview for Apache Cassandra support

Cassandra is a highly performant and scalable datastore of the BigTable family. The request for supporting it dates back to the early days of Hibernate OGM. So it’s a great pleasure to finally have this puppy out. Many thanks to all that worked on this journey: Michaël Figuière, Khanh Maudoux, John Worrell and Jonathan Halliday!

To use the Cassandra backend, add the hibernate-ogm-cassandra module to your classpath and specify the following properties for your persistence unit:

hibernate.ogm.datastore.provider = cassandra_experimental
hibernate.ogm.datastore.database = my_keyspace
hibernate.ogm.datastore.host = cassandra.example.com

The Cassandra backend is in “tech preview” state at this moment. We still have lots of work left on our plates (e.g. clustering support, alternative association persistence options, queries, documentation), but we think we have the right foundation now. Be warned that mapping details still may change. Still this is the right time for you to give this new backend a first try and let us know about your experiences with it.

Any feature requests, error reports and other feedback are very welcome. Especially any thoughts on the proposed mapping of domain model elements to Cassandra structures are of great interest for us.

Query improvements

We spent some more time to improve support for mapping JP-QL queries either to native NoSQL queries or full-text queries executed via Hibernate Search + Lucene.

You can now use types like enums or Date as query parameters:

List<Movie> movies = entityManager.createQuery( "SELECT m FROM Movie m WHERE m.genre IN (:genre)", Movie.class )
    .setParameter( "genre", EnumSet.of( Genre.DRAMA, Genre.COMEDY ) )
    .getResultList();

As you would expect it, the given parameter values are converted into the corresponding type used in the datastore and passed to the query engine.

Mapping fix for element collections with nested embeddables

This release fixes a bug related to the mapping of nested embeddables stored within element collections. This affects the supported document stores as well as Neo4j. E.g. consider the following entity model:

@Entity
public class Account {

    @ElementCollection
    List<Address> addresses;
}

@Embeddable
public class Address {
    String street;
    String city;
    AddressType type;
}

@Embeddable
public class AddressType {
    String name;
}

Previously, this would have been stored as follows e.g. in MongoDB:

{
    "_id" : "account-1",
    "addresses" : [
        {
            "street" : "Piazza del Colosseo, 1",
            "city" : "Rome",
            "name" : "work" <-- “flattened” representation of the nested embeddable
        },
        ...
    ]
}

Whereas it really should be mapped like this:

{
    "_id" : "account-1",
    "addresses" : [
        {
            "street" : "Piazza del Colosseo, 1",
            "city" : "Rome",
            “type” : {
                "name" : "work"
            }
        },
        ...
    ]
}

This is the mapping applied as of this new release. Note that you need to take care of existing records should have been working with such a model. Also please check out the migration notes for the details.

We don’t take such mapping changes lightly and try to avoid them as much as we can. As this really was a mapping error, we decided to fix it as soon as possible; in the future we also may provide compatibility options or some sort of migration tooling in similar situations.

Support for MongoDB 3 and MongoDB replica sets

We now also support MongoDB 3. In particular the new SCRAM-SHA-1 authentication strategy is working now. Migration should be smooth.

But the biggest improvement is support for replica sets. To quote MongoDB’s documentation, a replica set “is a group of mongod processes that maintain the same data set”. By using replica sets, your data is stored redundantly and protected against node failures through automatic fail-over between replica set members.

To support the notion of replica sets, Hibernate OGM’s “host” configuration option has been enhanced and now allows to specify a comma-separated list of hosts and host:port tuples:

hibernate.ogm.datastore.host=db1.example.com,db2.example.com,db3.example.com:29019

With this configuration Hibernate OGM would connect to three MongoDB nodes, using the default port 27017 for the nodes db1 and db2 and the explicitly given port 29019 for node db3.

Currently the MongoDB backend is the only one supporting several nodes, but other backends will follow, e.g. Cassandra.

New built-in types for boolean mapping

Requested by a user in our forum, we now support all the boolean mapping types known from Hibernate ORM: YesNoType (maps to character/String “Y” or “N”), TrueFalseType (maps to “T” or “F”) and NumericBooleanType (maps to 1 or 0).

You can use these types by means of the @Type annotation like so:

// maps as boolean; default   
private boolean myBoolean;

@Type(type = "true_false")
private boolean myBoolean;

@Type(type = "yes_no")
private boolean myBoolean;

@Type(type = "numeric_boolean")
private boolean myBoolean;

Any feedback to the 4.2.0.CR1 release is highly welcome. The change log tells in detail what's in there for you. Finally, some more useful links:

One of the fundamental principles of Hibernate/JPA is that any writes done through the Session/EntityManager APIs are collected in the persistence context and are only written to the database upon transaction commit. If needed, you can enforce the flushing of changes by explicitly invoking the flush() method. Also Hibernate may trigger a flush automatically, e.g. prior to executing a query. This strategy allows to perform many optimizations, e.g. only persisting the final state of an entity changed several times, make use of batched inserts and others.

Hibernate OGM follows this approach and uses transaction boundaries for demarcating units of work and triggering writes to the datastore. Naturally, this works just fine on transactional NoSQL stores such as Infinispan or Neo4j, but it poses some challenges on stores without full transactional semantics.

For instance consider a method of a stateless EJB (which automatically runs within a container-managed transaction) which inserts three entities. If an error occurs when inserting the last entity, the other two will already have been written. When using a non-transactional datastore, there is no way to rollback these already applied changes. Depending on the specific use case, this may be acceptable; But in other cases it is desirable to take some action, for example retrying the failed insertion or performing compensating writes for the succeeded insertions, effectively undoing them.

This is where our new error handling API comes in. It allows you to get notified upon failed datastore operations or transaction rollback attempts, providing you with the executed and failed operation(s). This e.g. enables you to:

  • log all applied operations
  • retry a failed operation (e.g. after timeouts)
  • make an attempt to compensate applied changes (by executing an inverse operation)

An example error handler

Let’s take a look at an example:

public class ExampleErrorHandler implements ErrorHandler {

    @Override
    public void onRollback(RollbackContext context) {
        // write all applied operations to a log file
        for ( GridDialectOperation appliedOperation : context.getAppliedGridDialectOperations() ) {
            switch ( appliedOperation.getType() ) {
                case INSERT_TUPLE:
                    EntityKeyMetadata entityKeyMetadata = appliedOperation.as( InsertTuple.class ).getEntityKeyMetadata();
                    Tuple tuple = appliedOperation.as( InsertTuple.class ).getTuple();

                    // write EKM and tuple to log file...
                    break;
                case REMOVE_TUPLE:
                    // ...
                    break;
                case ...
                    // ...
                    break;
            }
        }
    }

    @Override
    public ErrorHandlingStrategy onFailedGridDialectOperation(FailedGridDialectOperationContext context) {
        // Ignore this exception and continue
        if ( context.getException() instanceof TupleAlreadyExistsException ) {
            GridDialectOperation failedOperation = context.getFailedOperation();
            // write to log ...

            return ErrorHandlingStrategy.CONTINUE;
        }
        // But abort on all others
        else {
            return ErrorHandlingStrategy.ABORT;
        }
    }
}

The onRollback() method - which is called when the transaction (or unit of work) is rolled back (either by the user or by the container) - shows how to iterate over all datastore operations applied prior to the rollback attempt, examine their specific type and e.g. write them to a log file.

The onFailedGridDialectOperation() method is called for each specific datastore operation failing. It lets you decide whether to continue - ignoring the failure - or abort the operation. If ABORT is returned, the causing exception will be re-thrown, eventually causing the current transaction to be rolled back. If CONTINUE is returned, that exception will be ignored, causing the current transaction to continue.

The decision whether to abort or continue can be based on the specific exception type or on the grid dialect operation which caused the failure. In the example all exceptions of type TupleAlreadyExistsException are ignored (meaning we don't care about duplicate records being inserted into the datastore), whereas all other exceptions cause the current unit of work to be aborted. You also could react to datastore-specific exceptions such as MongoDB’s MongoTimeoutException, if needed.

Having created the ErrorHandler implementation, it needs to be registered with Hibernate OGM. You can do so for instance using a persistence unit property in META-INF/persistence.xml:

<property name="hibernate.ogm.error_handler" value="com.example.ExampleErrorHandler"/>

Feedback needed!

In its current form the API lays the ground for manually performing tasks such as retrying, logging and similar. But we envision a more automated approach in future versions, e.g. for automatic retries of failed operations.

Of course this API can and will not bring transactional guarantees to datastores which don't support them natively. If you find yourself frequently in a situation where you wish for transactional "all or nothing" semantics, you might consider to move to a datastore which actually provides this functionality. But you also may look into more suitable mapping approaches. For instance embeddables and element collections are a great way for ensuring atomicity of an entity and dependent structures on document stores such as MongoDB or CouchDB, as they will be mapped to a single, hierarchically structured document which can updated with a single datastore call.

At this point, the API is considered experimental and will evolve in future releases. For this, your input is very important to us! Let us know what you would like to do with such an API and what functionality should be added. Just tweet to @Hibernate or send a mail to our development list. If you like, you also may add comments to the JIRA issues addressing further development of the API: OGM-775 ("Introduce RETRY error handling strategy"), OGM-776 ("Provide means for executing compensating actions") and OGM-777 ("Expose more context information via error handler contexts").

Any feedback is greatly appreciated!

I am happy to announce the release of Hibernate OGM 4.1.3 Final and 4.2.0.Beta1!

The former brings several usability improvements, bug fixes and component upgrades, whereas the latter comes with new query features, an error handling API for non-transactional datastores and more.

The releases can be obtained as ZIP or TAR.GZ from SourceForge (4.1.3.Final, 4.2.0.Beta1) or via Maven, Gradle etc., using the following GAV coordinates:

  • org.hibernate.ogm:hibernate-ogm-core:4.1.3.Final or org.hibernate.ogm:hibernate-ogm-core:4.2.0.Beta1 for the Hibernate OGM engine and
  • org.hibernate.ogm:hibernate-ogm-<%DATASTORE%>:4.1.3.Final or org.hibernate.ogm:hibernate-ogm-<%DATASTORE%>:4.2.0.Beta1, depending on the backend you want to use.

Note that from 4.2 onwards, Hibernate OGM requires Java 7. We aimed at Java 6 compatibility for as long as possible, but with more and more of our backends and components requiring Java 7 (e.g. Neo4j and Lucene), we felt that it was time to move on and take advantage of the Java 7 goodies for the Hibernate OGM code base as well.

Let's dive into some of the new functionality of the 4.2 Beta1 release.

Query improvements

We continued our quest for extensive query capabilities and enabled more JP-QL constructs, namely queries on element collections. For example consider the following entity Order which contains an element collection of its order lines:

@Entity
public class Order {

    @Id
    @GeneratedValue
    private long id;

    @ElementCollection
    private List<OrderLine> orderLines = new ArrayList<>();

    // getter, setters etc. ...
}

@Embeddable
public class OrderLine {

    private String item;
    private BigDecimal price;

    // getter, setters etc. ...
}

To obtain a list of all orders which contain a wooden toy train order line, you could run the following JP-QL query:

List<Order> toyTrainOrders = entityManager
    .createQuery( "FROM Order o JOIN o.orderLines ol WHERE ol.item LIKE '%wooden toy train%'" )
    .getResultList();

As with other JP-QL queries, Hibernate OGM will convert the query string into a native query for MongoDB and Neo4j. For the other backends a Lucene-based full-text query will be created.

Note that the JOIN syntax at this point is only supported for element collections, i.e. you cannot create join queries for arbitrary associations to other entities. The reason is that most NoSQL stores don't support joins between different tables/collections, therefore such query can't be translated in a sensible way. An exception are graph databases such as Neo4j where joins can be converted into queries based on relationships (which is what actually happens for the element collection case), but we are not quite there yet :)

API for retrieving all executed and failed datastore operations

Hibernate ORM collects all write operations internally and propagates them to the database in most cases only upon transaction commit (in addition, changes will also be flushed prior to query execution or upon explicite flush() calls). This enables many optimizations such as batched inserts. Hibernate OGM follows this approach and also uses transaction boundaries for demarcating units of work and triggering writes to the datastore.

While this naturally works great with transactional NoSQL stores such as Infinispan or Neo4j, it poses some challenges on non-transactional datastores. The 4.2 release therefore provides the initial draft of an API which notifies you upon failed datastore operations, letting you take appropriate action such as e.g. logging all previously applied operations or retrying a failed operation.

In order to not let this release announcement grow too big, there is a separate post just dedicated to this new API.

Support for Fongo

Fongo ("Fake Mongo") is a very nice tool when it comes to testing MongoDB applications. It provides a pure in-memory implementation of the MongoDB Java API which allows to test MongoDB-based applications without starting up an actual MongoDB instance.

Thanks to community member Alex Soto, you can use Fongo now also for testing your Hibernate OGM based application. To do so, simply add the following dependencies to your project (assuming you work with Maven and have added the Hibernate OGM BOM to the dependencyManagement block of your POM file):

<dependencies>
    <dependency>
        <groupId>org.hibernate.ogm</groupId>
        <artifactId>hibernate-ogm-mongodb</artifactId>
    </dependency>
    <dependency>
        <groupId>com.github.fakemongo</groupId>
        <artifactId>fongo</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

Then specify "fongodb" as the Hibernate OGM datastore provider, e.g. via a persistence unit property in META-INF/persistence.xml:

<persistence-unit name="example-PU" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ogm.jpa.HibernateOgmPersistence</provider>
    <properties>
        <property name="hibernate.ogm.datastore.provider" value="fongodb"/>
    </properties>
</persistence-unit>

Hibernate OGM will now use Fongo rather than a real MongoDB server as the backend. As said, this can be quite handy when testing your application, just do not forget to switch to a real datastore when deploying to production ;-)

Usability improvements

Finally, let's take a quick look at two nice usability improvements which you already get with the update to 4.1.3.

The first is a better support for the RESOURCE_LOCAL transaction type when working with stores such as MongoDB under Java SE. As described above, Hibernate OGM relies on transaction demarcation to gather data changes and write them to the datastore in an optimized way. This used to require a JTA implementation on the classpath, also if the underlying datastore actually is not transactional. This is obsolete now, i.e. you can work with RESOURCE_LOCAL and MongoDB, CouchDB etc. without any further dependencies.

If you work with Hibernate OGM and Hibernate Search, you will be happy to learn that the creation of full-text queries became easier. It is not necessary any longer to configure the database retrieval method for each and every FullTextQuery, instead this happens automatically on a global level now when bootstrapping the Hibernate OGM engine.

It’s my pleasure to announce the release of Hibernate OGM 4.1.2!

This maintenance release fixes several bugs, e.g. around JP-QL, using Hibernate OGM in SaaS environments or mapping byte[] properties on CouchDB. We also clarified several issues in the reference guide. Many thanks to everyone test-driving Hibernate OGM and reporting bugs (and feature requests) to us!

As always you can get this release as ZIP or TAR.GZ from SourceForge or use the following GAV coordinates with your preferred build tool:

  • org.hibernate.ogm:hibernate-ogm-core:4.1.2.Final for the Hibernate OGM engine and
  • org.hibernate.ogm:hibernate-ogm-<%DATASTORE%>:4.1.2.Final, depending on the backend you want to use.

While preparing another release for fixing some more issues in Hibernate OGM 4.1, we take in parallel the first steps towards Hibernate OGM 4.2. You can expect many improvements in the field of JP-QL queries, support for custom types, an SPI for logging executed/failed operations on non-transactional NoSQL stores and much more. The first 4.2 Alpha will be out soon.

Finally, some useful links:

The first final version of Hibernate OGM is out and we’ve recovered a bit from the release frenzy. So we thought it’d be a good idea to begin the new year with a tutorial-style blog series, which shows how to get started with Hibernate OGM and what it can do for you. Just in case you missed the news, Hibernate OGM is the newest project under the Hibernate umbrella and allows you to persist entity models in different NoSQL stores via the well-known JPA.

We’ll cover these topics in the following weeks:

  • Persisting your first entities (this instalment)
  • Querying for your data
  • Running on WildFly
  • Running with CDI on Java SE
  • Store data into two different stores in the same application

If you’d like us to discuss any other topics, please let us know. Just add a comment below or tweet your suggestions to us.

In this first part of the series we are going to set up a Java project with the required dependencies, create some simple entities and write/read them to and from the store. We’ll start with the Neo4j graph database and then we’ll switch to the MongoDB document store with only a small configuration change.

Project set-up

Let’s first create a new Java project with the required dependencies. We’re going to use Maven as a build tool in the following, but of course Gradle or others would work equally well.

Add this to the dependencyManagement block of your pom.xml:

...
<dependencyManagement>
    <dependencies>
        ...
        <dependency>
            <groupId>org.hibernate.ogm</groupId>
            <artifactId>hibernate-ogm-bom</artifactId>
            <type>pom</type>
            <version>4.1.1.Final</version>
            <scope>import</scope>
        </dependency>
            ...
    </dependencies>
</dependencyManagement>
...

This will make sure that you are using matching versions of the Hibernate OGM modules and their dependencies. Then add the following to the dependencies block:

...
<dependencies>
    ...
    <dependency>
        <groupId>org.hibernate.ogm</groupId>
        <artifactId>hibernate-ogm-neo4j</artifactId>
    </dependency>
    <dependency>
        <groupId>org.jboss.jbossts</groupId>
        <artifactId>jbossjta</artifactId>
    </dependency>
    ...
</dependencies>
...

The dependencies are:

  • The Hibernate OGM module for working with an embedded Neo4j database; This will pull in all other required modules such as Hibernate OGM core and the Neo4j driver. When using MongoDB, you’d swap that with hibernate-ogm-mongodb.
  • JBoss' implementation of the Java Transaction API (JTA), which is needed when not running within a Java EE container such as WildFly

The domain model

Our example domain model is made up of three classes: Hike, HikeSection and Person.

There is a composition relationship between Hike and HikeSection, i.e. a hike comprises several sections whose life cycle is fully dependent on the Hike. The list of hike sections is ordered; This order needs to be maintained when persisting a hike and its sections.

The association between Hike and Person (acting as hike organizer) is a bi-directional many-to-one/one-to-many relationship: One person can organize zero ore more hikes, whereas one hike has exactly one person acting as its organizer.

Mapping the entities

Now let’s map the domain model by creating the entity classes and annotating them with the required meta-data. Let’s start with the Person class:

@Entity
public class Person {

    @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid2")
    private long id;

    private String firstName;
    private String lastName;

    @OneToMany(mappedBy = "organizer", cascade = CascadeType.PERSIST)
    private Set<Hike> organizedHikes = new HashSet<>();

    // constructors, getters and setters...
}

The entity type is marked as such using the @Entity annotation, while the property representing the identifier is annotated with @Id.

Instead of assigning ids manually, Hibernate OGM can take care of this, offering several id generation strategies such as (emulated) sequences, UUIDs and more. Using a UUID generator is usually a good choice as it ensures portability across different NoSQL datastores and makes id generation fast and scalable. But depending on the store you work with, you also could use specific id types such as object ids in the case of MongoDB (see the reference guide for the details).

Finally, @OneToMany marks the organizedHikes property as an association between entities. As it is a bi-directional entity, the mappedBy attribute is required for specifying the side of the association which is in charge of managing it. Specifying the cascade type PERSIST ensures that persisting a person will automatically cause its associated hikes to be persisted, too.

Next is the Hike class:

@Entity
public class Hike {

    @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid2")
    private String id;

    private String description;
    private Date date;
    private BigDecimal difficulty;

    @ManyToOne
    private Person organizer;

    @ElementCollection
    @OrderColumn(name = "sectionNo")
    private List<HikeSection> sections;

    // constructors, getters and setters...
}

Here the @ManyToOne annotation marks the other side of the bi-directional association between Hike and Organizer. As HikeSection is supposed to be dependent on Hike, the sections list is mapped via @ElementCollection. To ensure the order of sections is maintained in the datastore, @OrderColumn is used. This will add one extra "column" to the persisted records which holds the order number of each section.

Finally, the HikeSection class:

@Embeddable
public class HikeSection {

    private String start;
    private String end;

    // constructors, getters and setters...
}

Unlike Person and Hike, it is not mapped via @Entity but using @Embeddable. This means it is always part of another entity (Hike in this case) and as such also has no identity on its own. Therefore it doesn’t declare any @Id property.

Note that these mappings looked exactly the same, had you been using Hibernate ORM with a relational datastore. And indeed that’s one of the promises of Hibernate OGM: Make the migration between the relational and the NoSQL paradigms as easy as possible!

Creating the persistence.xml

With the entity classes in place, one more thing is missing, JPA's persistence.xml descriptor. Create it under src/main/resources/META-INF/persistence.xml:

<?xml version="1.0" encoding="utf-8"?>

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
    version="2.0">

    <persistence-unit name="hikePu" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.ogm.jpa.HibernateOgmPersistence</provider>

        <properties>
            <property name="hibernate.ogm.datastore.provider" value="neo4j_embedded" />
            <property name="hibernate.ogm.datastore.database" value="HikeDB" />
            <property name="hibernate.ogm.neo4j.database_path" value="target/test_data_dir" />
        </properties>
    </persistence-unit>
</persistence>

If you have worked with JPA before, this persistence unit definition should look very familiar to you. The main difference to using the classic Hibernate ORM on top of a relational database is the specific provider class we need to specify for Hibernate OGM: org.hibernate.ogm.jpa.HibernateOgmPersistence.

In addition, some properties specific to Hibernate OGM and the chosen back end are defined to set:

  • the back end to use (an embedded Neo4j graph database in this case)
  • the name of the Neo4j database
  • the directory for storing the Neo4j database files

Depending on your usage and the back end, other properties might be required, e.g. for setting a host, user name, password etc. You can find all available properties in a class named <BACK END>Properties, e.g. Neo4jProperties, MongoDBProperties and so on.

Saving and loading an entity

With all these bits in place its time to persist (and load) some entities. Create a simple JUnit test shell for doing so:

public class HikeTest {

    private static EntityManagerFactory entityManagerFactory;

    @BeforeClass
    public static void setUpEntityManagerFactory() {
        entityManagerFactory = Persistence.createEntityManagerFactory( "hikePu" );
    }

    @AfterClass
    public static void closeEntityManagerFactory() {
        entityManagerFactory.close();
    }
}

The two methods manage an entity manager factory for the persistence unit defined in persistence.xml. It is kept in a field so it can be used for several test methods (remember, entity manager factories are rather expensive to create, so they should be initialized once and be kept around for re-use).

Then create a test method persisting and loading some data:

@Test
public void canPersistAndLoadPersonAndHikes() {
    EntityManager entityManager = entityManagerFactory.createEntityManager();

    entityManager.getTransaction().begin();

    // create a Person
    Person bob = new Person( "Bob", "McRobb" );

    // and two hikes
    Hike cornwall = new Hike(
            "Visiting Land's End", new Date(), new BigDecimal( "5.5" ),
            new HikeSection( "Penzance", "Mousehole" ),
            new HikeSection( "Mousehole", "St. Levan" ),
            new HikeSection( "St. Levan", "Land's End" )
    );
    Hike isleOfWight = new Hike(
            "Exploring Carisbrooke Castle", new Date(), new BigDecimal( "7.5" ),
            new HikeSection( "Freshwater", "Calbourne" ),
            new HikeSection( "Calbourne", "Carisbrooke Castle" )
    );

    // let Bob organize the two hikes
    cornwall.setOrganizer( bob );
    bob.getOrganizedHikes().add( cornwall );

    isleOfWight.setOrganizer( bob );
    bob.getOrganizedHikes().add( isleOfWight );

    // persist organizer (will be cascaded to hikes)
    entityManager.persist( bob );

    entityManager.getTransaction().commit();

    // get a new EM to make sure data is actually retrieved from the store and not Hibernate's internal cache
    entityManager.close();
    entityManager = entityManagerFactory.createEntityManager();

    // load it back
    entityManager.getTransaction().begin();

    Person loadedPerson = entityManager.find( Person.class, bob.getId() );
    assertThat( loadedPerson ).isNotNull();
    assertThat( loadedPerson.getFirstName() ).isEqualTo( "Bob" );
    assertThat( loadedPerson.getOrganizedHikes() ).onProperty( "description" ).containsOnly( "Visiting Land's End", "Exploring Carisbrooke Castle" );

    entityManager.getTransaction().commit();

    entityManager.close();
}

Note how both actions happen within a transaction. Neo4j is a fully transactional datastore which can be controlled nicely via JPA’s transaction API. Within an actual application one would probably work with a less verbose approach for transaction control. Depending on the chosen back end and the kind of environment your application runs in (e.g. a Java EE container such as WildFly), you could take advantage of declarative transaction management via CDI or EJB. But let’s save that for another time.

Having persisted some data, you can examine it, using the nice web console coming with Neo4j. The following shows the entities persisted by the test:

Hibernate OGM aims for the most natural mapping possible for the datastore you are targeting. In the case of Neo4j as a graph datastore this means that any entity will be mapped to a corresponding node.

The entity properties are mapped as node properties (see the black box describing one of the Hike nodes). Any not natively supported property types will be converted as required. E.g. that’s the case for the date property which is persisted as an ISO-formatted String. Additionally, each entity node has the label ENTITY (to distinguish it from nodes of other types) and a label specifying its entity type (Hike in this case).

Associations are mapped as relationships between nodes, with the association role being mapped to the relationship type.

Note that Neo4j does not have the notion of embedded objects. Therefore, the HikeSection objects are mapped as nodes with the label EMBEDDED, linked with the owning Hike nodes. The order of sections is persisted via a property on the relationship.

Switching to MongoDB

One of Hibernate OGM's promises is to allow using the same API - namely, JPA - to work with different NoSQL stores. So let’s see how that holds and make use of MongoDB which, unlike Neo4j, is a document datastore and persists data in a JSON-like representation. To do so, first replace the Neo4j back end with the following one:

...
<dependency>
    <groupId>org.hibernate.ogm</groupId>
    <artifactId>hibernate-ogm-mongodb</artifactId>
</dependency>
...

Then update the configuration in persistence.xml to work with MongoDB as the back end, using the properties accessible through MongoDBProperties to give host name and credentials matching your environment (if you don’t have MongoDB installed yet, you can download it here):

...
<properties>
    <property name="hibernate.ogm.datastore.provider" value="mongodb" />
    <property name="hibernate.ogm.datastore.database" value="HikeDB" />
    <property name="hibernate.ogm.datastore.host" value="mongodb.mycompany.com" />
    <property name="hibernate.ogm.datastore.username" value="db_user" />
    <property name="hibernate.ogm.datastore.password" value="top_secret!" />
</properties>
...

And that’s all you need to do to persist your entities in MongoDB rather than Neo4j. If you now run the test again, you’ll find the following BSON documents in your datastore:

# Collection "Person"
{
    "_id" : "50b62f9b-874f-4513-85aa-c2f59015a9d0",
    "firstName" : "Bob",
    "lastName" : "McRobb",
    "organizedHikes" : [
        "a78d731f-eff0-41f5-88d6-951f0206ee67",
        "32384eb4-717a-43dc-8c58-9aa4c4e505d1"
    ]
}
# Collection Hike
{
    "_id" : "a78d731f-eff0-41f5-88d6-951f0206ee67",
    "date" : ISODate("2015-01-16T11:59:48.928Z"),
    "description" : "Visiting Land's End",
    "difficulty" : "5.5",
    "organizer_id" : "50b62f9b-874f-4513-85aa-c2f59015a9d0",
    "sections" : [
        {
            "sectionNo" : 0,
            "start" : "Penzance",
            "end" : "Mousehole"
        },
        {
            "sectionNo" : 1,
            "start" : "Mousehole",
            "end" : "St. Levan"
        },
        {
            "sectionNo" : 2,
            "start" : "St. Levan",
            "end" : "Land's End"
        }
    ]
}
{
    "_id" : "32384eb4-717a-43dc-8c58-9aa4c4e505d1",
    "date" : ISODate("2015-01-16T11:59:48.928Z"),
    "description" : "Exploring Carisbrooke Castle",
    "difficulty" : "7.5",
    "organizer_id" : "50b62f9b-874f-4513-85aa-c2f59015a9d0",
    "sections" : [
        {
            "sectionNo" : 1,
            "start" : "Calbourne",
            "end" : "Carisbrooke Castle"
        },
        {
            "sectionNo" : 0,
            "start" : "Freshwater",
            "end" : "Calbourne"
        }
    ]
}

Again, the mapping is very natural and just as you’d expect it when working with a document store like MongoDB. The bi-directional one-to-many/many-to-one association between Person and Hike is mapped by storing the referenced id(s) on either side. When loading back the data, Hibernate OGM will resolve the ids and allow to navigate the association from one object to the other.

Element collections are mapped using MongoDB's capabilities for storing hierarchical structures. Here the sections of a hike are mapped to an array within the document of the owning hike, with an additional field sectionNo to maintain the collection order. This allows to load an entity and its embedded elements very efficiently via a single round-trip to the datastore.

Wrap-up

In this first instalment of NoSQL with Hibernate OGM 101 you’ve learned how to set up a project with the required dependencies, map some entities and associations and persist them in Neo4j and MongoDB. All this happens via the well-known JPA API. So if you have worked with Hibernate ORM and JPA in the past on top of relational databases, it never was easier to dive into the world of NoSQL.

At the same time, each store is geared towards certain use cases and thus provides specific features and configuration options. Naturally, those cannot be exposed through a generic API such as JPA. Therefore Hibernate OGM lets you make usage of native NoSQL queries and allows to configure store-specific settings via its flexible option system.

You can find the complete example code of this blog post on GitHub. Just fork it and play with it as you like.

Of course storing entities and getting them back via their id is only the beginning. In any real application you’d want to run queries against your data and you’d likely also want to take advantage of some specific features and settings of your chosen NoSQL store. We’ll come to that in the next parts of this series, so stay tuned!

Showing 1 to 5 of 16 blog entries