JavaData & JavaHow to Manage Data Persistence with MongoDB and JPA

How to Manage Data Persistence with MongoDB and JPA

In the way that ORM maps a Java object into relational stores, OGM (Object/Grid Mapper) is a similar answer to a NoSQL database such as Mongo DB. NoSQL databases are inherently non-relational and schema less. Hibernate OGM extends the existing ORM infrastructure to bring forth the essence of (mongodb) document object mapping to POJO and vice-versa. The code for interfacing Hibernate OGM is similar to its ORM counterpart and acts as a JPA (Java Persistence Annotation) implementation for NoSQL data stores.

An Overview

Hibernate ORM basically uses a set of interfaces and classes to convert persistent data between Java and a relational database. When working in the Java EE environment, it is highly recommended that JPA bootstrapping of Hibernate be done through JTA (Java Transaction API). It’s intriguing; mongodb does not require following ACID properties and hence do not support transaction. Hibernate OGM mitigates its lacking (?) by queuing changes before committing them. Hibernate OGM also recommends using transaction demarcation with no rollback option. Perhaps this is the reason why bootstrapping through JTA is suggested.

Data
Figure 1: The Hibernate ORM

In view of the ORM paradigm, JPA is a tool that provides the means to interact with the help of Java objects rather than SQL statements in implementing CRUD operations. Here, too, Hibernate OGM acts in a similar manner. However, the biggest difference is that OGM does not need a JDBC as an in-between layer to interact with the database. It instead uses a data store provider and a data store dialect as an adapter between a NOSQL store and Hibernate OGM.

A data store provides acts as a connection manager, such as for mongodb. It is:

org.hibernate.ogm.datastore.mongodb.impl.MongoDBDatastoreProvider

And the data store dialect acts as a communication manager. For mongodb, it is:

org.hibernate.ogm.datastore.mongodb.MongoDBDialect

Tools Used

So, a minimal setting in persistence.xml to connect mongodb can be the following items.

1. The XML document begins with the following statements:

<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">

2. The persistence unit name can be any string and, as per recommendation transaction, the type must be declared as JTA. Another valid transaction type can be “RESOURCE_LOCAL.”

<persistence-unit name="ogmjpaPU" transaction-type="JTA">

3. The Hibernate OGM persistence provider is HibernateOgmPersistence, much like EclipseLink is to Glassfish, Hibernate to Jboss, OpenJPA to WebSphere, and so forth.

<provider>
   org.hibernate.ogm.jpa.HibernateOgmPersistence
</provider>
<properties>

4. In the property section, the first property to set is the JTA platform, which in our case is as follows:

<property name="hibernate.transaction.jta.platform"
   value="org.hibernate.service.jta.platform.internal
   .JBossStandAloneJtaPlatform"/>

5. Other properties are as follows:

<property name="hibernate.ogm.datastore.provider"
   value="org.hibernate.ogm.datastore.mongodb.impl
   .MongoDBDatastoreProvider"/>
<property name="hibernate.ogm.datastore.grid_dialect"
   value="org.hibernate.ogm.datastore.mongodb.MongoDBDialect"/>
<property name="hibernate.ogm.datastore.database"
   value="testdb"/>
<property name="hibernate.ogm.mongodb.host"
   value="127.0.0.1"/>
</properties>
</persistence-unit>
</persistence>

JPA Entity Classes

JPA enables us to define a class that represents an application data object model. Typically, the instances of these lightweight persistence domain classes are stored in the database. The EntityManager instance associated with the persistence context supplies CRUD operations that can be utilised to interact to and from the Java application and database (in our case, mongodb). In the following couple of examples, we have used a bidirectional relationship between the POJOs with the help of JPA. Recall that this is very much similar to using any persistence provider, such as Hibernate ORM or EclipseLink. There is nothing special here due to Hibernate OGM.

package org.mano.entity;

import java.io.Serializable;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;

@Entity
public class Person implements Serializable {

   @Id
   @GeneratedValue(strategy = GenerationType.AUTO)
   private int id;
   private String name;
   @OneToMany(mappedBy = "person",
      fetch = FetchType.LAZY)
   private List<Address> addresses;

   public Person() {
   }

   public Person(String name) {
      this.name = name;
   }
   // ...getters and setters
}

Listing 1: Person.java

package org.mano.entity;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;

@Entity
public class Address implements Serializable {

   @Id
   @GeneratedValue(strategy = GenerationType.AUTO)
   private int id;
   private String country;
   private String zip;
   @ManyToOne
   @JoinColumn(name = "person_id")
   private Person person;

   public Address() {
   }

   public Address(String country, String zip) {
      this.country = country;
      this.zip = zip;
   }

   // ...getters and setters
}

Listing 2: Address.java

EJB Classes

The EJB class in our example typically provides data transaction operations because a transaction is a fundamental part of an enterprise application. This class provides the database services we opted to expose via persistence context. Observe that some of the CRUD operations are written and that is via an EntityManager persistence provider. The transaction operation in our case would be handled by underlying JTA.

package org.mano.ejb;

import java.util.ArrayList;
import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import org.mano.entity.Address;
import org.mano.entity.Person;

@Stateless
public class PersonEJB {

   @PersistenceContext(unitName = "ogmjpaPU")
   private EntityManager em;

   public void createPerson(String name) {
      Person p = new Person(name);
      em.persist(p);
   }

   public void saveAddress(int personId, String country,
         String zip) {
      Person p = findPersonById(personId);
      if (p != null) {
         Address a = new Address(country, zip);
         a.setPerson(p);
         em.persist(a);
      }

   }

   public List<Address> findAllAddressOfPerson(int personId) {
      List<Address> list = new ArrayList<>();
      Person p = findPersonById(personId);
      if (p != null) {
         list = p.getAddresses();
      }
      return list;
   }

   public List<Person> findAllPerson() {
      List<Person> list = new ArrayList<>();
      Query q = em.createQuery("from Person");
      list = q.getResultList();
      return list;
   }

   public Person findPersonById(int id) {
      Query q = em.createQuery("from Person
         where id=:id");
      q.setParameter("id", id);
      return (Person) q.getSingleResult();
   }

   public Person findPersonByName(String name) {
      Query q = em.createQuery("from Person
         where name=:name");
      q.setParameter("name", name);
      return (Person) q.getSingleResult();
   }

}

Listing 3: PersonEJB.java

Controller Class

EJB classes should not be directly exposed to views. The following class (see Listing 4) acts as a controller to our application. We will add a CDI bean that will act as a wrapper between the view and the transactional layer.

package org.mano.controller;

import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.enterprise.inject.Model;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.event.ValueChangeEvent;
import javax.faces.model.SelectItem;
import javax.inject.Named;
import org.mano.ejb.PersonEJB;
import org.mano.entity.Address;
import org.mano.entity.Person;


@Model
public class PersonController {

   @Inject
   private PersonEJB ejb;

   private int personId;
   private String name;
   private String country;
   private String zip;

   private List<Address> listAddresses;
   private List<SelectItem> listPersons;

   @PostConstruct
   public void init() {
      getListPersons();
      if (listPersons.size() > 0) {
         personId = Integer.parseInt(listPersons.get(0)
            .getValue().toString());
         listAddresses =
            ejb.findAllAddressOfPerson(personId);
      }
   }

   public void createPerson() {
      ejb.createPerson(name);
      FacesMessage fm = new FacesMessage("Created
         Person " + name);
      FacesContext.getCurrentInstance().addMessage
         ("Message", fm);
      name = null;
      listPersons = null;
   }

   public void saveAddress() {
      ejb.saveAddress(personId, country, zip);
      FacesMessage fm = new FacesMessage("Saved address
         for" + name);
      FacesContext.getCurrentInstance()
         .addMessage("Message", fm);
      country = null;
      zip = null;
   }

   public void changeListener(ValueChangeEvent e) {
      Object old = e.getNewValue();
      personId =
         Integer.parseInt(e.getNewValue().toString());
      listAddresses =
         ejb.findAllAddressOfPerson(personId);
   }

   public void findAllPerson() {
      List<Person> list = ejb.findAllPerson();
      for (Person p : list) {
         listPersons.add(new SelectItem(p.getId(),
            p.getName()));
      }
   }

   public List<SelectItem> getListPersons() {
      if (listPersons == null) {
         listPersons = new ArrayList<>();
         findAllPerson();
      }
      return listPersons;
   }

   //...getters and setters
}

Listing 4: PersonController.java

Views

The application is composed of three views:

  • index.xhtml: The main view that loads up at start up
  • NewPerson.xhtml: Used to add new person details
  • NewAddress.xhtml: Used to add one or more address details to a particular person
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html 
      xmlns_h="http://java.sun.com/jsf/html"
      xmlns_f="http://java.sun.com/jsf/core">
   <h:head>  </h:head>
      <h:body>
         <h:panelGrid columns="1" border="1">
         <f:facet name="header">
            <h:outputText value="Address List"/>
         </f:facet>
         <h:form id="listAddressForm">
            <h:outputText value="Select Person:" />
            <h:selectOneMenu id="selectPerson"
                             valueChangeListener=
                                "#{personController.changeListener}"
                             onchange="submit()"
                             value="#{personController.personId}">
               <f:selectItems value="#{personController.listPersons}" />
            </h:selectOneMenu>
            <h:dataTable value="#{personController.listAddresses}"
                         var="pa" border="1" rowClasses="row1, row2"
                         headerClass="header">
               <h:column>
                  <f:facet name="header">
                     <h:outputText value="Name" />
                  </f:facet>
                  <h:outputText value="#{pa.person.name}" />
               </h:column>
               <h:column>
                  <f:facet name="header">
                     <h:outputText value="Country" />
                  </f:facet>
                  <h:outputText value="#{pa.country}" />
               </h:column>
               <h:column>
                  <f:facet name="header">
                     <h:outputText value="Zip" />
                  </f:facet>
                  <h:outputText value="#{pa.zip}" />
               </h:column>
            </h:dataTable>
            <h:commandButton action="NewPerson"
                             value="Insert Person" />
            <h:commandButton action="NewAddress"
                             value="Insert Address" />
         </h:form>
      </h:panelGrid>
      <h:messages />
   </h:body>
</html>

Listing 5: index.xhtml

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html 
      xmlns_h="http://xmlns.jcp.org/jsf/html"
      xmlns_f="http://xmlns.jcp.org/jsf/core">
   <h:head>
      <title>Facelet Title</title>
   </h:head>
   <h:body>
      <h:panelGrid columns="1" border="1">
         <f:facet name="header">
            <h:outputText value="New Person"/>
         </f:facet>
         <h:form id="newPerson">
            <h:panelGrid columns="2" border="1">
               <f:facet name="header">
                  <h:outputText value="Insert new Person" />
               </f:facet>
               <h:outputText value="Name" />
               <h:inputText value="#{personController.name}" />
               <h:commandButton action="#{personController.createPerson}"
                                value="Insert Person" />
               <h:commandButton action="index" value="Back" />
            </h:panelGrid>
         </h:form>
      </h:panelGrid>
      <h:messages />
   </h:body>
</html>

Listing 6: NewPerson.xhtml

<?xml version='1.0' encoding='UTF-8' ?&gt
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html 
      xmlns_h="http://xmlns.jcp.org/jsf/html"
      xmlns_f="http://xmlns.jcp.org/jsf/core">
   <h:head>
      <title>Facelet Title</title>
   </h:head>
   <h:body>
      <h:panelGrid columns="1" border="1">
         <f:facet name="header">
            <h:outputText value="New Order"/>
         </f:facet>
         <h:form id="newAddress">
            <h:panelGrid columns="2" border="1">
               <f:facet name="header">
                  <h:outputText value="Insert new Address" />
               </f:facet>
               <h:outputText value="Country" />
               <h:inputText value="#{personController.country}" />
               <h:outputText value="Zip" />
               <h:inputText value="#{personController.zip}" />
               <h:outputText value="Person" />
               <h:selectOneMenu id="selectPersonforAddress"
                                value="#{personController.personId}">
                  <f:selectItems value="#{personController.listPersons}" />
               </h:selectOneMenu>
               <h:commandButton action="#{personController.saveAddress}"
                                value="Save Address" />
               <h:commandButton action="index" value="Back" />
            </h:panelGrid>
         </h:form>
      </h:panelGrid>
      <h:messages/>
   </h:body>
</html>

Listing 7: NewAddress.xhtml

Conclusion

The article tried give you an idea of how to use the Hibernate OGM framework in dealing with a NoSQL database such as MongoDB. Hibernate ORM old timers would find many similarities. The obvious advantage of using OGM is in terms of abstractions using JPA is to simplify database interaction procedures. Hibernate OGM is still immature and can bring out glitches while programming. Also, watch out for the property setting in persistence.xml while programming; many faults are due to incorrect parameter values in the changing environment/platform. Always consult the OGM manual according to the version used to get a clue. Happy coding!

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories