Configuring Hibernate OGM for your JBoss app using MongoDB

What is Hibernate OGM?

Hibernate Object/Grid Mapping, provides Java Persistence (JPA) support for NoSQL databases like MongoDB. With Hibernate OGM, you can easily store data in a NoSQL datastores using the Hibernate Core engine.

Hibernate OGM should not be confused with other Hibernate projects like Hibernate Search, Hibernate Validator and Hibernate ORM. Like Hibernate OGM, these subprojects focus on enabling developers to utilize POJO-style domain models in their applications.

Why Hibernate OGM?

Most applications do more reading than writing from storage. The relational models of traditional RDBMS tend to deliver good query performance by default, even with multi-table cascade queries, known as a “join”.

NoSQL queries perform really well when the ID is known. But query performance tends to suffer when it is not.

That’s why we need Hibernate OGM.

It adds some annotation in entities, and defines the fields which need to be indexed. When you save or update entities via Hibernate OGM, or ORM(for RDBMS) (Hibernate Search has been integrated with either of them very well), Hibernate Search will create Lucene index for this object. Then you can do full-text query for the saved entity via Hibernate Search Query API or Lucene Query API.

Let’s get our hands dirty

Before we start, I wanna introduce JBoss Developer Framework, a new project from JBoss community. It’s designed for helping developers to understand and use JBoss technology better. There is a lot of courses, videos, articles and migration guides(from Java EE 5 to EE6, from Spring to Java EE) etc. You can learn Java EE 6 step by step, and also new tech like REST or HTML5.

This blog starts from kitchensink, a sample from JBoss Developer Community.

The latest code of kitchensink can be found here

This sample project uses following features:

  • Bean Validation 1.0
  • EJB 3.1
  • JAX-RS
  • JPA 2.0
  • JSF 2.0
  • CDI 1.0
  • Arquillian

You will need JDK 6/7, Maven 3 and JBoss AS 7 to run this sample. (All supported on OpenShift)

Note: Some dependencies can only be found in JBoss maven repository. So we may need update settings.xml of maven with JBoss maven repo. Please take MavenGettingStarted and JBossAS quickstarts as references.

Some Tech Codes

First one is persistence.xml (main/resources/META-INF/persistence.xml), with default paths:

    <persistence version="2.0"
       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">
       <persistence-unit name="primary">
          <jta-data-source>java:jboss/datasources/KitchensinkQuickstartDS</jta-data-source>
          <properties>
             <property name="hibernate.hbm2ddl.auto" value="create-drop" />
             <property name="hibernate.show_sql" value="false" />
          </properties>
       </persistence-unit>
    </persistence>

It’s a simple file with one data source and two attributes. “hibernate.hbm2ddl.auto=creat-drop” here means it will automatically create/drop table structure when the session factory is created/closed.

Also, we can find main/resources/import.sql. It will be imported automatically by Hibernate when Hibernate finishes the creation of table structure, in order to append some initial data.

Besides, referenced data source is defined in main/webapp/WEB-INF/kitchensink-quickstart-ds.xml.

Then let’s look at the other part of this blog, entity definition:

    @Entity
    @XmlRootElement
    @Table(uniqueConstraints = @UniqueConstraint(columnNames = "email"))
    public class Member implements Serializable {
       /** Default value included to remove warning. Remove or modify at will. **/
       private static final long serialVersionUID = 1L;
 
       @Id
       @GeneratedValue
       private Long id;
 
       @NotNull
       @Size(min = 1, max = 25)
       @Pattern(regexp = "[A-Za-z ]*", message = "must contain only letters and spaces")
       private String name;
 
       @NotNull
       @NotEmpty
       @Email
       private String email;
 
       @NotNull
       @Size(min = 10, max = 12)
       @Digits(fraction = 0, integer = 12)
       @Column(name = "phone_number")
       private String phoneNumber;    
       // getters / setters
    } 

It’s a simple entity mapping. javax.xml.bind.annotation.XmlRootElement is an annotation of JAXB. This entity object can be transferred into XML.

And this entity defines some annotation of bean validation. Please check the documents of Hibernate Validator for more details.

Okay, enough for the introductions of techs. Let’s go deeper into the project.

Do some update for NoSQL

This sample uses the data source from JBoss AS 7 – java:jboss/datasources/KitchensinkQuickstartDS.
So let’s try to make this sample to replace H2 with MongoDB as its data store with as less change as possible.

First of all, adding the dependency. Since we are trying to save entity objects via Hibernate OGM, the following dependencies are what we need here:

        <dependency>
            <groupId>org.hibernate.ogm</groupId>
            <artifactId>hibernate-ogm-core</artifactId>
            <version>4.0.0-SNAPSHOT</version>
            <scope>provided</scope>
        </dependency>        
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.6.6</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate.ogm</groupId>
            <artifactId>hibernate-ogm-mongodb</artifactId>
            <version>${hibernateOgmVersion}</version>
            <scope>provided</scope>
        </dependency>

Then goes the persistence.xml file. Hibernate OGM itself is an entity of JPA, but we still need to declare explicitly that we will use Hibernate OGM as the entity of JPA, because the default one in JBoss AS7 is Hibernate ORM 4.

     <persistence version="2.0"
       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">
       <persistence-unit name="primary">
          <provider>org.hibernate.ogm.jpa.HibernateOgmPersistence</provider>
          <properties>
              <property name="hibernate.search.Rules.directory_provider" value="ram"/>
              <property name="hibernate.ogm.datastore.provider"
                        value="org.hibernate.ogm.datastore.mongodb.impl.MongoDBDatastoreProvider"/>
              <property name="hibernate.ogm.mongodb.database" value="NOSQL_DB_NAME"/>
              <property name="hibernate.ogm.mongodb.host" value="NOSQL_DB_HOST"/>
              <property name="hibernate.ogm.mongodb.port" value="NOSQL_DB_PORT"/>
              <property name="hibernate.ogm.mongodb.username" value="NOSQL_DB_USERNAME"/>
              <property name="hibernate.ogm.mongodb.password" value="NOSQL_DB_PASSWORD"/>
          </properties>
          <class>org.jboss.as.quickstarts.kitchensink.model.Member</class>
          <class>org.jboss.as.quickstarts.kitchensink.model.ContactDetails</class>
      </persistence-unit>
    </persistence>

In this updated file we can find:

  • Use org.hibernate.ogm.jpa.HibernateOgmPersistence as the entity of JPA
  • Remove the reference of data source; Hibernate OGM doesn’t use RMDBS
  • Declare MongoDB as the data store by hibernate.ogm.datastore.provider
  • Declare MongoDB properties by hibernate.ogm.mongodb.*

And src/main/webapp/WEB-INF/kitchensink-quickstart-ds.xml can be removed now.

These are all we need to update for configuration. Easy, right?

Now we have switched from RMDBS to MongoDB on storge level via Hibernate OGM. But we still some more update because of the differences between RMDBS and NoSQL.

Use UUID as primary key

We usually use @GeneratedValue to retrieve the id auto-generated by database when working on Hibernate ORM. Also, we usually set id as long type in order to get better performance.

But on NoSQL (K-V type) databases, they don’t have such primary key. So we are going to use UUID as primary key for unifying globally. And this strategy is already provided in Hibernate OGM.

org.jboss.as.quickstarts.kitchensink.model.Member#id should looks like:

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

After altered the type of id, the next one is org.jboss.as.quickstarts.kitchensink.rest.MemberResourceRESTService#lookupMemberById

        @GET
        @Path("/{id:[0-9][0-9,\\-]*}")
        @Produces(MediaType.APPLICATION_JSON)
        public Member lookupMemberById(@PathParam("id") String id) {
            Member member = repository.findById(id);
            if (member == null) {
                throw new WebApplicationException(Response.Status.NOT_FOUND);
            }
            return member;
        }

This method supplies a function to query Member by REST API. Since we have already altered the type of id to String, we need one more update for @Path then it will be able to accept characters.

@Path("/{id:[0-9,a-z][0-9,a-z,\\-]*}")
Query

Some functions of Hibernate OGM (like Criteria query) have not been completed yet because it’s such a young project.
Fortunately, there is a pretty good integration between Hibernate OGM and Hibernate Search, so we can use Hibernate Search for this part.

We have done enough code talk here.
You can get all the updated codes here.

Deply on JBoss AS7 on your machine

This a maven project, so you need to install maven first.
Since there is cargo plugin (by Hardy) in the pom, you don’t have to download JBoss AS7 manually. Cargo will do all the downloading and deploying stuff.

  • Compile – mvn clean package
  • Run – mvn cargo:run
  • Test(based on Arquillian) – mvn test

Then you can try it on http://127.0.0.1:8080/ogm-kitchensink.

BTW, it will through an error message when you input an invalid name, phone number or email address. That’s the automatic input validation provided by Bean Validation.

Okay! Now you have a Hibernate OGM + NoSQL application running on JBoss AS7.

Besides, JBoss AS7 is fast when starting, very fast!

23:27:10,050 INFO  [org.jboss.as] (Controller Boot Thread) JBAS015874: 
JBoss AS 7.1.1.Final "Brontes" started in 2009ms - Started 133 of 208 services (74 services are passive or on-demand)

That log was its start time on my PC, pretty much the same as Tomcat. But, JBoss AS7 is an application server with full Java EE6 certification, and Tomcat is just a servlet container.

Hardy also provided a tool for us to generate test data – member-generator.rb
Before using it, you need several gem:

gem install httparty
gem install nokogiri
gem install choice

Then it will generate some test data like:

ruby member-generator.rb -a http://localhost:8080/ogm-kitchensink -c 20

Deploy on OpenShift (Finally!)

Don’t worry if you have no IDE or maven or anything I mentioned earlier. It’s all provided on OpenShift.

After created your account and namespace, you can simply deploy this Hibernate OGM sample with a few commands:

rhc app create ogm jbossas-7
rhc cartridge add mongodb-2.0 -a ogm
cd ogm
git remote add upstream -m master git://github.com/openshift/openshift-ogm-quickstart.git
git pull -s recursive -X theirs upstream master
git push

That’s all, you can now check it out by your app URL.
Isn’t it quick?

Last but not least, you can check more quickstarts like this one on OpenShift Github page.

Chinese blog using Infinispan as data store by Strong Liu from Red Hat JBoss team.
Thanks to Strong for his close help and the great blog!

Categories
JBoss, MongoDB
Tags
Comments are closed.