Help

Inactive Bloggers
04. Mar 2015, 13:55 CET, by Sanne Grinovero

Now that feedback is coming about our great 5.0 release, it's time to publish quite a maintenance version as we have a long list of improvements already! So today we release Hibernate Search 5.1.0.Final.

Performance improvements

The indexing engine learned yet another smart trick, and is going to generate more efficient delete and update operations in case you have a non-trivial hierarchy of entities being mapped. See also HSEARCH-1767 for more details. This might not affect you at all, or it might give you a very significant performance boost!

Also affecting performance, the caching code for Filters improved and this might result in a higher cache hit ratio.

Many usability improvements

  • It is no longer required to provide a key object for parameterized Filters (we'll figure it out automatically). (HSEARCH-295)
  • You can now use annotations to declare Filters and Analyzers on packages or super classes! After all, these are global. (HSEARCH-1763, HSEARCH-633)
  • When booting programmatically, you can now inject an instance of an ErrorHandler (HSEARCH-1624)
  • The programmatic configuration API was improved by adding some missing methods

Projection: bugfixes

There was a bug in projecting values of embeddable types which could strike occasionally, as it would trigger only on some specific iterations of the metadata, which is unsorted so it could manifest only occasionally. This is fixed now as HSEARCH-1786.

Many thanks to Rustem Sagimbekov, Marc Schipperheyn and Ildar Mussin for reporting it and helping me diagnose the problem.

Multi-tenancy

Some users have recently been trying to use Hibernate Search combined with Hibernate ORM's multi-tenancy features. We have to admit this wasn't tested! We added a warning in the documentation, as some more work is going to be needed to make for a flawless integration experience.

There was one blocker preventing people to use the MassIndexer HSEARCH-1649 with multi-tenancy, which is fixed now. So while there are still some limitations documented here, you should be able to move forward. Please let us know if more changes are needed (on top of the known limitations, which are easy to work around for now).

OSGi improvements

With 5.0 we published our first experimental support for depoying in OSGi, and it's maturing quickly thanks to all your feedback! Thanks to Gustavo Nalle and Andy Phillips for the latest suggestions and improvements.

Dropped

The optional serialization module based on plain standard Java serialization was removed. Please use hibernate-search-serialization-avro, which has always been the better implementation. If you had strong reasons to love the plain java implementation, let us know.

Components upgrades

  • Apache Lucene released 4.10.4
  • Infinispan upgraded to 7.1.1

Get it now!

Everything you need is available on Hibernate Search's web site. Download the full distribution from here. And don't hesitate to reach us in our forums.

Stackoverflow

If you prefer to use stackoverflow.com, please use the tag hibernate-search. And if you have a moment to help other users, some please consider registering to the hibernate-search tag to help us answering all the questions.

If you are new to Hibernate Search, best is to start with our getting started guide. And remember: feedback, comments and/or pull-requests are welcome on the website too.

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:

We are happy to announce the release of Hibernate Validator 5.2.0.Beta1. This time a whole bunch of Hibernate team members (Davide, Emmanuel, Gunnar, Sanne and myself) joined forces to make this release happen. Thanks everyone!

In total we were able to resolve 30 issues. Here are some highlights.

Ability to provide external ClassLoader

There are several cases in which Hibernate Validator needs to load resources or classes:

  • XML descriptors (META-INF/validation.xml as well as XML constraint mappings)
  • Classes specified by name in XML descriptors
  • ValidationMessages resource bundle

By default Hibernate Validator tries to load these resources via the current thread context classloader. If that’s not successful, Hibernate Validator’s own class loader will used as a fall-back. For cases where this strategy is not appropriate (e.g. modularized environments such as OSGi), you can now provide an external class loader during bootstrapping:

Validator validator = Validation.byProvider( HibernateValidator.class )
    .configure()
    .externalClassLoader( classLoader )
    .buildValidatorFactory()
    .getValidator();

Hibernate Validator features.xml for Apache Karaf

As a side effect of the class loader improvements we were able to write some OSGi integration tests between Apache Karaf and Hibernate Valdiator. The tests themselves are not so important, but we decided to release the required Karaf features.xml file as part of our regular build artifacts. You can view the first version of this file here.

Programmatic constraint mapping via ConstraintMappingContributor

In Hibernate Validator you can, as a provider specific feature, programmatically define constraint mappings. So far this feature was tied to a programmatic bootstrapping of the ValidatorFactory itself. However, often you are not bootstrapping the factory yourself, but work with the default factory as configured via META-INF/validation.xml. In these cases you can now add one or more programmatic constraint mappings by implementing a ConstraintMappingContributor:

public static class MyConstraintMappingContributor implements ConstraintMappingContributor {

	@Override
	public void createConstraintMappings(ConstraintMappingBuilder builder) {
		builder.addConstraintMapping()
			.type( Marathon.class )
				.property( "name", METHOD )
					.constraint( new NotNullDef() )
				.property( "numberOfHelpers", FIELD )
					.constraint( new MinDef().value( 1 ) );

		builder.addConstraintMapping()
			.type( Runner.class )
				.property( "paidEntryFee", FIELD )
					.constraint( new AssertTrueDef() );
	}
}

In order for this ConstraintMappingContributor to get picked up by the bootstrap process, you then need to specify the fully-qualified class name of the contributor implementation in META-INF/validation.xml, using the property key hibernate.validator.constraint_mapping_contributor.

ConstraintDefinitionContributor with resource bundles

We introduced ConstraintDefinitionContributor already in the Alpha1 release, but it so far only allowed you to contribute constraints. Default messages via a resource bundle were not covered. This has changed, and you can just place a ContributorValidationMessages.properties and/or its locale-specific specializations at the root your JAR. Hibernate Validator will aggregate the entries from all the bundles with this name found on the classpath.

Where do I get it?

Maven artifacts for this release are in the JBoss Maven repository (GAV org.hibernate:hibernate-validator:5.2.0.Beta1) and on SourceForge.

Feedback and questions are welcome via the Hibernate Validator forum or on stackoverflow using the hibernate-validator tag. If that is not enough, check out the other ways you can get in contact with us.

Enjoy!

Welcome to the second part of the NoSQL with Hibernate OGM 101 tutorial series. In part 1 you have seen how to include Hibernate OGM in your Java project and how simple it is to persist entities and switch between different kind of NoSQL datastores.

Now that your datastore contains some data you will probably want to run some queries on it. Fear not, Hibernate OGM will let you get your data in several different ways:

  • using the Java Persistence Query Langage (JP-QL)
  • using the NoSQL native query language of the datastore of your choice (if it has one)
  • using Hibernate Search queries - primarly full-text queries

All of these alternatives will allow you to run a query on the datastore and get the result as a list of managed entities.

Preparing the test class

We are going to add a new class HikeQueryTest. It will populate the datastore with some information about hikes:

public class HikeQueryTest {

    private static EntityManagerFactory entityManagerFactory;

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

            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();
           entityManager.close();
    }

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

This methods will make sure that the entity manager factory is created before running the tests and that the datastore contains some data. The data are the same we stored in part 1.

Now that we have some data in place, we can start to write some tests to search for them.

Using the Java Persistence Query Langage (JP-QL)

The JP-QL is a query language defined as part of the Java Persistence API (JPA) specification. It's designed to work with entities and to be database independent.

Taking the entity Hike as an example:

@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, setters, ...
}

A JP-QL query to get the list of available hikes ordered by difficulty looks like this:

SELECT h FROM Hike h ORDER BY h.difficulty ASC

Hibernate OGM will parse this query and transform it into the equivalent one in the native query language of the datastore of your choice. In Neo4j, for example, it creates and execute a Cypher query like the following:

MATCH (h:Hike) RETURN h ORDER BY h.difficulty

In MongoDB, using the MongoDB JavaScript API as a query notation, it looks like this:

db.Hike.find({}, { "difficulty": 1})

If you use JP-QL in your application you will be able to switch between datastore without the need to update the queries.

Now that you have an understanding of what’s going on, we can start querying for the data we persisted. We can, for example, get the list of available hikes:

@Test
public void canSearchUsingJPQLQuery() {
    // Get a new entityManager
    EntityManager entityManager = entityManagerFactory.createEntityManager();

    // Start transaction
    entityManager.getTransaction().begin();

    // Find all the available hikes ordered by difficulty
    List<Hike> hikes = entityManager
        .createQuery( "SELECT h FROM Hike h ORDER BY h.difficulty ASC" , Hike.class )
        .getResultList();

    assertThat( hikes.size() ).isEqualTo( 2 );
    assertThat( hikes ).onProperty( "description" ).containsExactly( "Visiting Land's End", "Exploring Carisbrooke Castle" );

    entityManager.getTransaction().commit();
    entityManager.close();
}

If you have used the JPA specification before you will find this code very familiar: it's the same code you would write when working on a relational database using JPA.

You can test this by switching the configuration and dependency between Neo4j and MongoDB: the test will still pass without any change in the code.

The cool thing is that you can use JP-QL queries with datastores which don't have their own query engine. Hibernate OGM's query parser will create full-text queries in this case which are executed via Hibernate Search and Lucene. We will see later how you can do this in more details.

The result of the query is a list of managed entities. This means that changes to the objects will be applied to the data in the database automatically. You also can navigate the resulting object graph, causing lazy associations to be loaded as required.

The support for the JP-QL language is not complete and it might change depending on the backend. We will leave the details to the official Hibernate OGM documentation. At the moment what's supported is:

  • simple comparisons
  • IS NULL and IS NOT NULL
  • the boolean operators AND, OR, NOT
  • LIKE, IN and BETWEEN
  • ORDER BY

In case JP-QL is not a good fit for your use case, we will see how you can execute a query using the native language of the backend of your choice.

Using the native backend query language

Sometimes you might decide to sacrifice portablility in favor of the power of the underlying native query language. For example, you might want to benefit from the abilities of Neo4j's Cypher language for running hierarchical/recursive queries. Using MongoDB, let’s get the hikes passing through “Penzance”:

// Search for the hikes with a section that start from "Penzace" in MongoDB
List<Hike> hikes = entityManager.createNativeQuery("{ $query : { sections : { $elemMatch : { start: 'Penzance' } } } }", Hike.class ).getResultList();

The same code with Neo4j would look like this:

// Search for the hikes with a section that start from "Penzace" in Neo4j
List<Hike> hikes = entityManager.createNativeQuery( "MATCH (h:Hike) -- (:Hike_sections {start: 'Penzance'} ) RETURN h", 
Hike.class ).getResultList();

The important thing to notice is that, like JPA queries, the objects returned by the query are managed entities.

You can also define queries using the annotation javax.persistence.NamedNativeQuery:

@Entity
@NamedNativeQuery(
name = "PenzanceHikes",
query = "{ $query : { sections : { $elemMatch : { start: 'Penzance' } } } }", resultClass = Hike.class )
public class Hike { … }

and then execute it like this:

List<Hike> hikes = entityManager.createNamedQuery( "PenzanceHikes" ).getResultList();

Using Hibernate Search queries

Hibernate Search offers a way to index Java objects into Lucene indexes and to execute full-text queries on them. The indexes do live outside your datastore. This means you can have query capabilities even if they are not supported natively. It also offers a few interesting properties in terms of feature set and scalability. In particular, using Hibernate Search, you can off-load query execution to separate nodes and scale it independently from the actual datastore nodes.

For this example we are going to use MongoDB. You first need to add Hibernate Search to your application. In a Maven project, you need to add the following dependency in the pom.xml:

<dependencies>
    ...
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-search-orm</artifactId>
    </dependency>
    ...
</dependencies>

Now, you can select what you want to index:

@Entity
@Indexed
public class Hike {

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

    @Field
    private String description;

    private Date date;
    private BigDecimal difficulty;

    @ManyToOne
    private Person organizer;

    @ElementCollection
    @OrderColumn(name = "sectionNo")
    private List<HikeSection> sections;
       
    // constructors, getters, setters, ...
}

The @Indexed annotation identifies the classes that we want to index, while the @Field annotation specifies which properties of the class we want to index. Every time a new Hike entity is persisted via the entity manager using Hibernate OGM, Hibernate Search will automatically add it to the index and keep track of changes to managed entities. That way, index and datastore are up to date.

You can now look for the hikes to Carisbrooke using Lucene queries. In this example, we will use the query builder provided by Hibernate Search:

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

    entityManager.getTransaction().begin();

    //Add full-text superpowers to any EntityManager:
    FullTextEntityManager ftem = Search.getFullTextEntityManager(entityManager);

    // Optionally use the QueryBuilder to simplify Query definition:
    QueryBuilder b = ftem.getSearchFactory().buildQueryBuilder().forEntity( Hike.class ).get();

    // A Lucene query to search for hikes to the Carisbrooke castle:
    Query lq = b.keyword().onField("description").matching("Carisbrooke castle").createQuery();

    //Transform the Lucene Query in a JPA Query:
    FullTextQuery ftQuery = ftem.createFullTextQuery(lq, Hike.class);

    //This is a requirement when using Hibernate OGM instead of ORM:
    ftQuery.initializeObjectsWith( ObjectLookupMethod.SKIP, DatabaseRetrievalMethod.FIND_BY_ID );

    // List matching hikes
    List<Hike> hikes = ftQuery.getResultList();
    assertThat( hikes ).onProperty( "description" ).containsOnly( "Exploring Carisbrooke Castle" );

    entityManager.getTransaction().commit();
    entityManager.close();
}

The result of the code will be a list of hikes mentioning “Carisbrooke castle” in the description.

Hibernate Search is a very powerful tool with many different options, it would take too long to describe all of them in this tutorial. You can check the reference documentation to learn more about it.

Wrap up

That’s all for now.

As you have seen, Hibernate OGM provides you with a range of options to run queries against your datastore, which should cover most of your typical query needs: JP-QL, native NoSQL queries and full-text queries via Hibernate Search / Apache Lucene. Even if you have never worked with NoSQL datastores before, you will be able to experiment with them easily.

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

Now that you know how to store and find your entities, we will see in the next part of the series how you can put everything inside an application container like WildFly.

We are eager to know your opinion, feel free to comment or contact us, we will answer your questions and hear your feed-back.;

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 1241 blog entries