Help
No profile available.
Archive

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.;

We are getting closer to a final release and this version is mainly about improving general performance and reducing the amount of round trips to the datastore. We also added optimistic locking support for datastores which provide atomic find-and-update operations.

We also worked on several bug fixes and improvements under the hood, you can read more about it in the release note.

Optimistic locking detection

With Hibernate ORM, you can add optimistic locking capability to an entity using the @Version annotation:


@Entity
public class Flight implements Serializable {
...
    @Version
    @Column(name="OPTLOCK")
    public Integer getVersion() { ... }
} 


So far Hibernate OGM’s support for this was limited to CouchDB, which has its own proprietary optimistic locking mechanism. In this release we added support for optimistic locking also for those datastores that provide atomic find-and-update semantics such as MongoDB.

As always, you can either download a release bundle from SourceForge or get the JARs from the JBoss Nexus repository server using Maven, Gradle etc. The GAV coordinates are:

  • org.hibernate.ogm:hibernate-ogm-core:4.1.0.Beta8 for the OGM engine and
  • org.hibernate.ogm:hibernate-ogm-<datastore>:4.1.0.Beta8, depending on the backend you want to use.

What's next?

We are currently focused is on improving performance, documentation, polishing the API and fixing last minute bugs.

You're very welcome to raise your voice on the mailing list, ask questions in the forum or report any bugs or feature requests in the issue tracker.

It's my pleasure to announce a new release of Hibernate OGM.

Hibernate OGM can now convert JP-QL queries into cypher queries when working with Neo4j. We improved the JSON representation used for associations in CouchDB and MongoDB making it more concise. We also worked on several bug fixes and improvements under the hood, you can read more about it in the release note.

As always, you can either download a release bundle from SourceForge or retrieve the JARs from the JBoss Nexus repository server using Maven, Gradle etc. The GAV coordinates are:

  • org.hibernate.ogm:hibernate-ogm-core:4.1.0.Beta5 for the OGM engine and
  • org.hibernate.ogm:hibernate-ogm-<datastore>:4.1.0.Beta5, depending on the backend you want to use.

From JP-QL to Cypher

For example, if you execute the following JP-QL query:

from Hypothesis h where h.author IN ('alma', 'alfred')

OGM will execute the following Cypher query on Neo4j:

MATCH (h:Hypothesis) WHERE ANY(_x_ IN ["alma", "alfred"] WHERE h.author = _x_) RETURN h

The following subset of JP-QL constructs is available at the moment:

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

More natural mapping for associations in MongoDB and CouchDB

In the previous releases, an entity with an association looked something like the following JSON:

{
   "_id": "4f5b48ad",
   ...
   "rows": [
       {
           "bankAccounts_id": "7873a2a7"
       }
   ]
}

We got rid of the name of the id in the rows field, this will now look like:

{
   "_id": "4f5b48ad",
   ...
   "rows": [
       {
           "7873a2a7"
       }
   ]
}

What's next?

Some work on the Neo4j side is still required to make the mapping of the entities more natural. We also want to add caching in several places to improve performance (OGM-541, OGM-515, OGM-522).

We are also discussing about a solution for the generation of error reports with the failed operations on non-transactional db.

You're very welcome to raise your voice on the mailing list, ask questions in the forum or report any bugs or feature requests in the issue tracker.

I'm happy to announce a new release of Hibernate OGM.

The MongoDB backend now supports the MongoDB CLI syntax for native queries. In Neo4j, we have solved a bug related to the way we store embedded collections (OGM-549) and we now create only one relationship for bidirectional associations. We have also worked on the compatibility with WildFLy 8.1. You can find more details about this release on JIRA.

As always, you can either download a release bundle from SourceForge or retrieve the JARs from the JBoss Nexus repository server using Maven, Gradle etc. The GAV coordinates are:

  • org.hibernate.ogm:hibernate-ogm-core:4.1.0.Beta4 for the OGM engine and
  • org.hibernate.ogm:hibernate-ogm-<datastore>:4.1.0.Beta4, depending on the backend you want to use.

Support for MongoDB CLI syntax

You can now specify queries using the MongoDB CLI syntax as shown in the following example:

    @Entity
    class Poem {
       ...
       String name;
       String author;
       ...
    }

    String poemsQuery = "db.Poem.find({'$query': { 'author': 'Oscar Wilde' }, '$orderby': { 'name': 1 }})";

    EntityManager em = ...
    List<Poem> oscarWildePoems = (List<Poem>)em.createNativeQuery( poemsQuery, Poem.class )
                                 .getResultList();

Currently only find() and count() queries are supported via the CLI syntax.

One relationship for bidirectional associations

In Neo4j, it is possible to navigate a relationship in both directions at the same speed. We moved to a more natural mapping using one relationship (instead of two) for a bidirectional association.

What's next?

For the Neo4j backend, we plan to make the mapping for one-to-one relationships more natural, remove redundant properties and translate JP-QL queries into native Cypher queries.

We will also work on the generation of error reports with the failed operations on non-transactional db.

You're very welcome to raise your voice on the mailing list, ask questions in the forum or report any bugs or feature requests in the issue tracker.

30. Oct 2013, 16:00 CET, by Davide D'Alto

After one month from the previous release we are happy to announce the new Hibernate OGM 4.0.0.Beta4.

Initial embedded Neo4j integration

Hibernate OGM can now work with Neo4j, a fully transactional property graph database.

Graph databases represent the data as a system of interconnected nodes and work well when you need to store information like the relationship between people in social networks. In addition, in a property graph, you can also add properties to the elements of the graph.

Hibernate OGM maps an entity as a node where the attributes of the entity become properties of the node. At the moment we add some additional properties for the conversion of the node into the entity but we are planning to replace them using the label mechanism added in the latest Neo4j versions.

The relationship between two nodes represents an association between two entities. Currently a bidirectional association requires two relationships but we are going to change this since Neo4j can navigate a relationship in both directions.

The integration with Neo4j is experimental but we are planning on improving it soon. Please, let us know what you think or help us improving it.

Native query support for MongoDB

One missing feature in the previous releases was the ability to retrieve managed entities using the query language of the database of your choice. This is particularly useful if the query language supports specific features unavailable to JP-QL or currently non implemented.

We started to work on this feature and it is now possible to execute a MongoDB query using the org.hibernate.Session or the javax.persistence.EntityManager.

Let's look at an example using the session:

List<OscarWildePoem> result = session
    .createSQLQuery( "{ $query : { author : 'Oscar Wilde' }, $orderby : { name : 1 } }" )
    .addEntity( OscarWildePoem.TABLE_NAME, OscarWildePoem.class )
    .list();

and one using the entity manager:

List<OscarWildePoem> results = entityManager
    .createNativeQuery( "{ $query : { author : 'Oscar Wilde' }, $orderby : { name : 1 } }", OscarWildePoem.class )
    .getResultList();

How to try it

You can take a look at the documentation or check how you can download Hibernate OGM 4.0.0.Beta4.

Many thanks to all the contributors that made this release possible whether it be via pull requests, bug reports or feedback on the forum.

Showing 1 to 5 of 6 blog entries