© Joseph B. Ottinger, Jeff Linwood and Dave Minter 2016

Joseph B. Ottinger, Jeff Linwood and Dave Minter, Beginning Hibernate, 10.1007/978-1-4842-2319-2_12

12. Leaving the Relational Database Behind: NoSQL

Joseph B. Ottinger, Jeff Linwood2 and Dave Minter3

(1)Youngsville, North Carolina, USA

(2)Austin, Texas, USA

(3)London, UK

Hibernate doesn’t limit you to using purely relational databases; Hibernate OGM1 stands as a project that can carry an object model into the realm of the NoSQL datastore, allowing developers to use a familiar mechanism to access multiple types of storage.

Relational databases are tabular in nature; sets of data (“tables”) are homogenous and regular, often described in terms of rows and columns. Relationships are fixed; a table can require that the value of one column be represented as a column or set of columns in another table. Rows cannot change shape or form (or size); the row and column sizes are generally fixed.

NoSQL datastores are more easily described in negative terms: they’re typically not tabular, data structures are not fixed, relationships are not enforced, data types for a given attribute are not consistent. That’s not to say that NoSQL data stores can’t have these characteristics. It just means that NoSQL datastores tend to be able to turn the rules of traditional databases on their heads. This can yield massive scalability or flexibility. Scalability in this context can refer to speed or size; Cassandra, for example, can use a cell concept, distributed among rows and columns – such that if you wanted, you could theoretically have a single row with two billion pieces of data.2

Flexible data structures mean that you can represent your data as a tree, or a set of trees, just as correctly as you can represent your data as a map, a set of values associated with a single key.3

The biggest problem with NoSQL is the object/data impedance mismatch. With a relational database, if your table has four columns in it, you can easily model that with an object. Each column would map to an object attribute, and that attribute’s type is presumably easily derived from the database metadata. However, most NoSQL databases don’t follow a mandated structure.4 A single item of a given type might have four attributes, but another item of the same type might have 15 attributes, or 70, or 400.

Because of the lack of mandated structure, objects can be difficult to design well, if at all,5 and many NoSQL Java APIs either ignore object design altogether (where information is represented simply as a stream of data) or enforce regular structures on a data storage system where regular structures aren’t the norm. As a result, migrating an application to NoSQL can be a problem to address.

In the end, NoSQL is not necessarily better than a relational database as much as it’s merely different. NoSQL datastores have their own challenges and strengths, and whether you can leverage them properly or not depends on the specific use case you have and your own expertise. Contrast that with relational systems, which are well known and much understood in terms of strengths and weaknesses; looking at the well of experience, we find for most problems SQL is by far the most appropriate solution, if only because so many people know what SQL can do and how to do it.

This chapter will not be able to tell you everything you will ever need to know about NoSQL. One of the strengths and weaknesses of the NoSQL landscape is that it’s not restricted by the definition of SQL, such that a NoSQL implementation can target specific use cases very well, being a master of one aspect of data rather than a jack of all trades, as the relational model is.

Where Is Hibernate When It Comes to NoSQL?

Hibernate can serve as a mapper for nonrelational datastores too, through Hibernate OGM. OGM is an adaptive layer between a strict object model and NoSQL’s typically rather flexible object model.

From the Java coder’s perspective, usage of Hibernate is fairly typical,6 in most respects; one defines objects annotated with @Entity, and interaction is through a standard Hibernate Session or JPA EntityManager object. It’s worth noting that integration was a problem in older versions of Hibernate, sometimes requiring the use of EntityManager and Session; this has been fixed, and you can now use whichever API you like, but there are a few warnings here.

First Warning: NoSQL Is Not Relational, and Hibernate Is an ORM

Hibernate is an object-relational mapper (an ORM), and has been designed to bridge the gap between relational models and object models. There’s a lot of similarity in how you see data in Java and how you see it in SQL, but it’s not a perfect match – that’s why some things are not trivial to do with an ORM.

It’s fair to say that the integration between SQL and Java isn’t perfect or seamless.

NoSQL databases take that a step further; they can follow the SQL model (in which case integration is fairly easy) or they can have a completely different internal paradigm. As such, Hibernate’s support is sometimes “bolted on” and features might not be fully implemented.

With that said, Hibernate does a fantastic job of allowing access to NoSQL databases, considering the challenges involved. In many cases, with a simple configuration change, everything just works. (We’ll be seeing this as we go through this chapter.)

There are exceptions to the integration, which range from minor to severe. This is unavoidable, because of the mismatch of features between SQL and NoSQL.7 Most of the exceptions center on queries, because NoSQL databases typically index and query differently from how SQL does. Hibernate provides a query language layer for NoSQL, but there’s no guarantee of feature completeness between SQL and NoSQL. (All things considered, though, Hibernate does a fantastic job at it, as stated.) This is less of a problem than it could be, because accessing a NoSQL datastore as if it were an SQL database can have negative performance implications. You don’t necessarily want to have an exact correspondence between feature sets.

Hibernate Is not Perfect, or “Finished”

The second warning is that not everything works perfectly, every time. Hibernate’s NoSQL support is very much a work in progress.

The Hibernate 5 implementation for NoSQL is far improved over the previous versions, with much better query language support and general API support; it used to be that using the full range of features required using JPA’s EntityManager for some features and Hibernate’s Session for others.

Now, things are much better; you can choose to stay Hibernate native (and thus use Session) or you can choose JPA’s interface (and thus use EntityManager) without having to use both. This is very good… but because the implementation is a moving target, some things are still problematic. Be prepared! Some things may work perfectly, and others may not – and examining the problem could show you that you’re using the API incorrectly, or that the implementation has a bug.

So what we’re going to do is demonstrate OGM and full-text search using the JPA interface; afterward, we’ll show one of the same tests, written to use Hibernate’s Session object for completeness’s sake and to demonstrate the similarities between the two APIs.

So let’s get started.

Basic CRUD Operations

What we’d like to do is see a set of operations done with Hibernate, with a few NoSQL back ends. We’ll test writes, reads, updates, and deletes, with some queries thrown in for good measure. Our object model will be very simple, because mapping from Java to a nonrelational backing store really should be tuned for each NoSQL implementation. There’s no good generalized solution here.8

Our project has a unique set of dependencies. Obviously, we need to include OGM and drivers for the specific NoSQL back ends we’re using. The Maven project file can be seen in Listing 12-1. Note that the Hibernate version is older than the most recent version of Hibernate used in the rest of the book; Hibernate OGM is trailing the core Hibernate project. This will affect some of the APIs we use, mainly in that query results aren’t able to be explicitly specified to return typed objects.9

Listing 12-1. The OGM Project’s pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
             http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <groupId>com.autumncode.books.hibernate</groupId>
    <artifactId>chapter12</artifactId>
    <version>1.0-SNAPSHOT</version>
    <modelVersion>4.0.0</modelVersion>


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


    <dependencies>
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>[6.9.10,)</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate.ogm</groupId>
            <artifactId>hibernate-ogm-mongodb</artifactId>
            <version>5.0.1.Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate.ogm</groupId>
            <artifactId>hibernate-ogm-infinispan</artifactId>
            <version>[5.0.1.Final,)</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate.javax.persistence</groupId>
            <artifactId>hibernate-jpa-2.1-api</artifactId>
        </dependency>
        <dependency>
            <groupId>org.jboss.spec.javax.transaction</groupId>
            <artifactId>jboss-transaction-api_1.2_spec</artifactId>
        </dependency>
        <dependency>
            <groupId>org.jboss.narayana.jta</groupId>
            <artifactId>narayana-jta</artifactId>
        </dependency>
        <!-- included for compilation of Hibernate-native OGM example -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <!--<version>[5.2.2.Final,)</version>-->
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Listing 12-2 shows our entire object model, a Person object, containing an ID, a name, and a balance. We’re going to eliminate the boilerplate methods again; Lombok could help, but let’s keep the listing more focused.

Listing 12-2. The Object Model for our NoSQL Venture, in src/main/java/chapter12/Person.java
package chapter12;

import org.hibernate.search.annotations.Analyze;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Indexed;


import javax.persistence.*;

@Entity
@Indexed
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Field(name = "person_id")
    Integer id;
    @Field(analyze = Analyze.NO)
    @Column
    String name;
    @Field(analyze = Analyze.NO)
    @Column
    Integer balance;


    public Person() {
    }


    public Person(String name, int balance) {
        this.name = name;
        this.balance = balance;
    }
    // we’re not showing all of the accessors, mutators, etc
}

We’ve introduced some new annotations: @Indexed and @Field. These will be important for searching our data, in the event that HQL isn’t supported or doesn’t fit our needs. These annotations come from Hibernate Search, which provides a bridge between an object model and a search engine (Lucene,10 in this case); the annotations tell Hibernate Search what factors to use for searching and how the elements should be used.

We’re going to configure two persistence units via JPA for our examples; the persistence.xml file looks like Listing 12-3.

Listing 12-3. The two NoSQL Persistence Units, Both Using the Same Object Model
<?xml version="1.0"?>
<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="chapter12-mongo" transaction-type="RESOURCE_LOCAL">
        <!-- Use Hibernate OGM provider: configuration will be transparent -->
        <provider>org.hibernate.ogm.jpa.HibernateOgmPersistence</provider>
        <class>chapter12.Person</class>
        <properties>
            <property name="hibernate.ogm.datastore.provider" value=
               "org.hibernate.ogm.datastore.mongodb.impl.MongoDBDatastoreProvider"/>
            <!-- defines which JTA Transaction we plan to use -->
            <property name="hibernate.ogm.datastore.database" value="chapter12"/>
            <property name="hibernate.ogm.datastore.create_database" value="true" />
            <property name="hibernate.search.default.indexBase" value="./lucene-mongo" />
            <property name="hibernate.transaction.jta.platform" value=
               "org.hibernate.service.jta.platform.internal.JBossStandAloneJtaPlatform"/>
        </properties>
    </persistence-unit>


    <persistence-unit name="chapter12-ispn" transaction-type="RESOURCE_LOCAL">
        <!-- Use Hibernate OGM provider: configuration will be transparent -->
        <provider>org.hibernate.ogm.jpa.HibernateOgmPersistence</provider>
        <class>chapter12.Person</class>
        <properties>
            <!-- property optional if you plan and use Infinispan, otherwise
                adjust to your favorite NoSQL Datastore provider. -->
            <property name="hibernate.ogm.datastore.provider" value=
             "org.hibernate.ogm.datastore.infinispan.impl.InfinispanDatastoreProvider"/>
            <!-- defines which JTA Transaction we plan to use -->
            <property name="hibernate.transaction.jta.platform" value=
              "org.hibernate.service.jta.platform.internal.JBossStandAloneJtaPlatform"/>
            <property name="hibernate.search.default.indexBase" value="./lucene-ispn" />
        </properties>
    </persistence-unit>
</persistence>

The code to acquire a working EntityManager from this is trivial. If we want the chapter12-ispn persistence unit, we could use the following snippet of code:

EntityManagerFactory factory = null;
public synchronized EntityManager getEntityManager() {
    if (factory == null) {
        factory = Persistence.createEntityManagerFactory(“chapter12-ispn”);
    }
    return factory.createEntityManager();
}

The Tests

Since we have two persistence units, we could create an abstract base class for the tests that contains the actual tests themselves, with extending classes providing specific custom information for the test – like the persistence unit name. Listing 12-4 shows a skeleton BaseOGMTest.java, to which we’ll add some tests as we progress.

Listing 12-4. BaseJPAOGMTest.java Skeleton
package chapter12.jpa;

import chapter12.Person;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.FullTextQuery;
import org.hibernate.search.jpa.Search;
import org.hibernate.search.query.DatabaseRetrievalMethod;
import org.hibernate.search.query.ObjectLookupMethod;
import org.hibernate.search.query.dsl.QueryBuilder;
import org.testng.annotations.Test;


import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.TypedQuery;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;


import static org.jgroups.util.Util.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;


public abstract class BaseJPAOGMTest {

    abstract String getPersistenceUnitName();

    EntityManagerFactory factory = null;

    public synchronized EntityManager getEntityManager() {
        if (factory == null) {
            factory = Persistence.createEntityManagerFactory(getPersistenceUnitName());
        }
        return factory.createEntityManager();
    }
}

Extending classes would provide a definition for getPersistenceUnitName(), and any methods annotated with @Test in either class can be executed.

Testing Create and Read

Listing 12-5 shows our first test; this test creates a Person, persists it, and then reads it. After we see the code, we’ll create an InfinispanTest class that extends BaseJPAOGMTest, which will give us enough to actually run our code.

Listing 12-5. The testCR Method , Which Tests Creates and Reads
@Test
public void testCR() {
    EntityManager em = getEntityManager();
    em.getTransaction().begin();
    Person person = new Person("user 1", 1);
    em.persist(person);
    em.getTransaction().commit();
    em.close();


    System.out.println(person);
    em = getEntityManager();
    Person p2 = em.find(Person.class, person.getId());
    em.close();
    System.out.println(p2);


    assertNotNull(p2);
    assertEquals(p2, person);
}

One thing that stands out about this code is that it is idiomatic normal Java persistence. We’re not using anything here that would indicate that we weren’t using a standard JPA entity manager.

As we see in Listing 12-6, the InfinispanTest class is very simple, but it provides the persistence unit name we’ll be using for Infinispan, as well as forcibly clearing the data before every test:

Listing 12-6. The Full Definition for InfinispanTest.java
package chapter12.jpa;

import org.hibernate.Query;
import org.hibernate.Session;
import org.testng.annotations.BeforeMethod;


import javax.persistence.EntityManager;

public class InfinispanTest extends BaseJPAOGMTest {
    @Override
    String getPersistenceUnitName() {
        return "chapter12-ispn";
    }


    @BeforeMethod
    public void clearInfinispan() {
        EntityManager em = getEntityManager();
        em.getTransaction().begin();
        Session session = em.unwrap(Session.class);
        Query q = session.createQuery("from Person p");
        for (Object p : q.list()) {
            System.out.println("removing " + p);
            em.remove(p);
        }
        em.getTransaction().commit();
        em.close();
    }
}

Testing Updates

Now let’s take a look at test for updates, in Listing 12-7; this will also follow the standard JPA idiom. (We will not, however, use the lambda mechanism we introduced in Chapter 10. We could, but then we’d have to duplicate the code and… let’s not.)

Listing 12-7. Testing NoSQL Updates with JPA
@Test
public void testUpdate() {
    EntityManager em = getEntityManager();
    em.getTransaction().begin();
    Person person = new Person("user 2", 1);
    em.persist(person);
    em.getTransaction().commit();
    em.close();


    em = getEntityManager();
    em.getTransaction().begin();
    Person p2 = em.find(Person.class, person.getId());
    p2.setBalance(2);
    em.getTransaction().commit();
    em.close();


    em = getEntityManager();
    em.getTransaction().begin();
    Person p3 = em.find(Person.class, person.getId());
    em.getTransaction().commit();
    em.close();


    assertEquals(p3, p2);
}

As usual, we acquire the EntityManager; we start a transaction; we load a managed Person reference, update it, and commit the transaction. This is very much the same process that we use for any such updates; from the coder’s perspective, we don’t know if we’re using PostgreSQL or Infinispan as a datastore.11

Testing Removal

As one might expect, removing entities remains idiomatic as well, as Listing 12-8 shows:

Listing 12-8. Removing Entities with JPA
@Test
public void testDelete() {
    EntityManager em = getEntityManager();
    em.getTransaction().begin();
    Person person = new Person("user 3", 1);
    em.persist(person);
    em.getTransaction().commit();
    em.close();


    em = getEntityManager();
    em.getTransaction().begin();
    Person p2 = em.find(Person.class, person.getId());
    em.remove(p2);
    em.getTransaction().commit();
    em.close();


    em = getEntityManager();
    em.getTransaction().begin();
    Person p3 = em.find(Person.class, person.getId());
    em.getTransaction().commit();
    em.close();


    assertNull(p3);
}

Querying in OGM

Queries over a NoSQL database can be interesting, because the storage engines differ so much from implementation to implementation. However, Hibernate OGM has you covered, for most cases: it is able to translate JPQL (the Java Persistence Query Language) and HQL (Hibernate Query Language) into native queries for the NoSQL engines.

There are likely to be outliers in terms of full support, because the underlying data stores may not support everything, so you may have to play around with queries in order to get them to work perfectly. It’s likely to be a factor in nearly everything related to OGM – because we’re trying to map a wide variety of data storage ideas into an object model.

Listing 12-9 shows how we populate our data set. We not only persist data but also preserve the entities in a Map so we can validate the returned data. Listing 12-10 shows a JPQL query that uses this data.

Listing 12-9. Populating Some Data for Our HQL Tests
Map<Integer, Person> people = new HashMap<>();
EntityManager em = getEntityManager();
em.getTransaction().begin();


for (int i = 4; i < 7; i++) {
    people.put(i, new Person("user " + i, i));
    em.persist(people.get(i));
}


em.getTransaction().commit();
em.close();
Listing 12-10. A Simple HQL Query
em = getEntityManager();
em.getTransaction().begin();
TypedQuery<Person> query=em.createQuery(
    "from Person p where p.balance = :balance",
    Person.class);
query.setParameter("balance", 4);
System.out.println(query.getResultList());
Person p = query.getSingleResult();
em.getTransaction().commit();
em.close();


assertEquals(p, people.get(4));

As you can see, we’re using idiomatic JPQL here; we don’t have to do anything special to look for data, even though the data storage engine is very much unlike the typical SQL data storage.

Our selection statement (the “where clause”) can combine elements, as shown in Listing 12-11:

Listing 12-11. Using AND in a “Where Clause” in HQL
em = getEntityManager();
em.getTransaction().begin();
query=em.createQuery(
    "from Person p where p.balance = :balance and p.name=:name",
    Person.class);
query.setParameter("balance", 4);
query.setParameter("name", "user 4");
p = query.getSingleResult();
assertEquals(p, people.get(4));


em.getTransaction().commit();
em.close();

Lastly, we can use comparison operators too, although here we’ll see yet another potential difference between SQL and NoSQL.

The concept of uniqueness in NoSQL isn’t quite the same as it is in relational models; it’s possible, depending on the datastore, to retrieve multiple copies of the same entity for a given query.12 As a result, we might get two values from our simple query, even though it should return only one; here, we’re adding the list of results into a Set<Person>, which will trim duplicates and give us single references for each entity. Our code is shown in Listing 12-12.

Listing 12-12. Using More Comparison Operators in HQL
em = getEntityManager();
em.getTransaction().begin();


query=em.createQuery("from Person p where p.balance > :balance", Person.class);
query.setParameter("balance", 4);
Set<Person> peopleList = new HashSet<Person>(query.getResultList());
assertEquals(peopleList.size(), 2);


em.getTransaction().commit();
em.close();

This leads us to Hibernate Search , which provides a programmatic interface to Apache Lucene. If an entity is indexed, its fields will be stored in a Lucene directory , which means they’re available for full-text queries.

To mark an entity as being indexed for Lucene, one simply adds the @Indexed annotation to the class, as we did for Person. The fields are annotated with @Field; the default is to have the fields analyzed, which makes them appropriate for use in a full-text, fuzzy search.13 If we want to use the fields verbatim for searches, we want to turn analysis off, which is done with @Field(analyze = Analyze.NO).

Actually executing the query involves creating a FullTextEntityManager, then building a Lucene query with that; one then converts the Lucene query into something that Hibernate can use to return entities. Lucene is even more prone to returning single entities multiple times, so one has to trim the result set for uniqueness here as well. Listing 12-13 shows an example of querying on a simple value with Hibernate Search, including populating data:

Listing 12-13. A Simple Hibernate Search Example
@Test
public void testQueryLucene() {
    Map<Integer, Person> people = new HashMap<>();
    EntityManager em = getEntityManager();
    em.getTransaction().begin();


    for (int i = 7; i < 9; i++) {
        people.put(i, new Person("user " + i, i));
        em.persist(people.get(i));
    }


    em.getTransaction().commit();
    em.close();


    em = getEntityManager();
    em.getTransaction().begin();
    FullTextEntityManager ftem = Search.getFullTextEntityManager(em);


    //Optionally use the QueryBuilder to simplify Query definition:
    QueryBuilder b = ftem.getSearchFactory()
            .buildQueryBuilder()
            .forEntity(Person.class)
            .get();
    org.apache.lucene.search.Query lq =
            b.keyword().onField("id").matching(people.get(7).getId()).createQuery();


    //Transform the Lucene Query in a JPA Query:
    FullTextQuery ftQuery = ftem.createFullTextQuery(lq, Person.class);
    //This is a requirement when using Hibernate OGM instead of ORM:
    ftQuery.initializeObjectsWith(ObjectLookupMethod.SKIP,
            DatabaseRetrievalMethod.FIND_BY_ID);


    Set<Person> resultList = new HashSet<>(ftQuery.getResultList());
    System.out.println(resultList);
    // lucene can return multiple results for a given query!
    Set<Person> personSet = new HashSet<>();
    personSet.addAll(resultList);
    assertEquals(personSet.size(), 1);
    em.getTransaction().commit();
    em.close();
}

This looks like a lot of code to accomplish something very simple, but it’s worth noting that we’re also able to execute full-text queries with similar code. Full-text and fuzzy queries aren’t trivial; the Hibernate Search code isn’t entirely trivial either, but it’s fairly simple.

All of this code runs against Infinispan fairly well, but what happens if we switch to another datastore, such as MongoDB?

MongoDB

MongoDB ( http://www.mongodb.org ) is an open source document database using JSON as a serialization format. It can shard well, has full indexing, and performs atomic operations for single documents; it’s also incredibly fast.

Installing mongodb is very easy.

  • On Windows, it’s as simple as downloading the appropriate Windows installer and running it.

    Note The same process works for other operating systems, as well, but the Linux and MacOSX package managers have mongodb available.

  • On Linux, Fedora users can install the mongodb-server packages by using sudo yum install mongodb-server; Ubuntu users can do the same with sudo apt-get install mongodb-server.

  • MacOSX users can install it with brew install mongodb.

Once mongodb is installed, you can start mongo for our tests by going to a temporary working directory and executing the following command:

mongod --dbpath . --noauth

This will run through a few steps for initialization, then wait for connection.

For us to use mongodb as a back-end datastore instead of Infinispan, all we need to do is provide the mongodb persistence unit’s name. Listing 12-14 is our example MongoTest.java code:

Listing 12-14. Using mongodb Instead of Infinispan for Our Datastore
package chapter12.jpa;

import com.mongodb.MongoClient;
import com.mongodb.client.MongoDatabase;
import org.testng.annotations.AfterMethod;


public class MongoTest extends BaseJPAOGMTest {
    @AfterMethod
    public void clearDB() {
        try (MongoClient mongoClient = new MongoClient()) {
            MongoDatabase db = mongoClient.getDatabase("chapter12");
            db.drop();
        }
    }


    @Override
    String getPersistenceUnitName() {
        return "chapter12-mongo";
    }
}

Apart from that, all of our tests should run cleanly. We have preserved the benefit of using a library for persistence, even while giving ourselves the chance to leverage the features that NoSQL datastores offer us.

What the Hibernate Native API for OGM Looks Like

Here, we’re going to only show a few test cases – the InfiniSpan and MongoDB implementations won’t change much more than our JPA variants would, and most of the code is quite similar except that it uses Session instead of EntityManager. Hibernate Search uses org.hibernate.search as a package instead of org.hibernate.search.jpa, but the usage is very much the same.

First, the simplified BaseHibernateOGMTest.java class, which serves as a direct analog to our BaseJPAOGMTest.java source – the test methods would be added to this.

package chapter12.hibernate;

import chapter12.Person;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.ogm.OgmSessionFactory;
import org.hibernate.ogm.boot.OgmSessionFactoryBuilder;
import org.hibernate.ogm.cfg.OgmProperties;
import org.hibernate.Query;
import org.hibernate.search.FullTextQuery;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.Search;
import org.hibernate.search.query.DatabaseRetrievalMethod;
import org.hibernate.search.query.ObjectLookupMethod;
import org.hibernate.search.query.dsl.QueryBuilder;


import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.testng.annotations.Test;


import static org.testng.Assert.*;

public abstract class BaseHibernateOGMTest {
    abstract String getConfigurationName();


    private final OgmSessionFactory factory;

    public BaseHibernateOGMTest() {
        StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
                .applySetting(OgmProperties.ENABLED, true)
                //assuming you are using JTA in a non container environment
                .applySetting(AvailableSettings.TRANSACTION_COORDINATOR_STRATEGY, "jta")
                //assuming JBoss TransactionManager in standalone mode
                .applySetting(AvailableSettings.JTA_PLATFORM, "JBossTS")
                //assuming Infinispan as the backend, using the default settings
                .applySetting(OgmProperties.DATASTORE_PROVIDER, getConfigurationName())
                .build();
        factory = new MetadataSources(registry)
                .addAnnotatedClass(Person.class)
                .buildMetadata()
                .getSessionFactoryBuilder()
                .unwrap(OgmSessionFactoryBuilder.class)
                .build();
    }


    protected Session getSession() {
        return factory.openSession();
    }
}

The first test we’ll replicate is the testCR() test, which shows creation and reads of data.

@Test
public void testCR() {
    Person person, p2;
    try (Session session = getSession()) {
        Transaction tx = session.beginTransaction();


        person = new Person("user 1", 1);
        session.save(person);
        tx.commit();
    }
    System.out.println(person);
    try (Session session = getSession()) {
        Transaction tx = session.beginTransaction();


        p2 = session.byId(Person.class).load(person.getId());
        tx.commit();
    }
    System.out.println(p2);
    assertNotNull(p2);
    assertEquals(p2, person);
}

Now let’s take a look at the Hibernate Search method, which – again – mirrors the JPA version very closely.

@Test
public void testQueryLucene() {
    Map<Integer, Person> people = new HashMap<>();
    try (Session session = getSession()) {
        Transaction tx = session.beginTransaction();


        for (int i = 7; i < 9; i++) {
            people.put(i, new Person("user " + i, i));
            session.save(people.get(i));
        }
        tx.commit();
    }


    try (Session session = getSession()) {
        Transaction tx = session.beginTransaction();
        FullTextSession ftem = Search.getFullTextSession(session);


        //Optionally use the QueryBuilder to simplify Query definition:
        QueryBuilder b = ftem.getSearchFactory()
                .buildQueryBuilder()
                .forEntity(Person.class)
                .get();
        org.apache.lucene.search.Query lq =
                b.keyword().onField("id").matching(people.get(7).getId()).createQuery();


        //Transform the Lucene Query in a JPA Query:
        FullTextQuery ftQuery = ftem.createFullTextQuery(lq, Person.class);
        //This is a requirement when using Hibernate OGM instead of ORM:
        ftQuery.initializeObjectsWith(ObjectLookupMethod.SKIP,
                DatabaseRetrievalMethod.FIND_BY_ID);


        Set<Person> resultList = new HashSet<>(ftQuery.list());
        System.out.println(resultList);
        // lucene can return multiple results for a given query!
        Set<Person> personSet = new HashSet<>();
        personSet.addAll(resultList);
        assertEquals(personSet.size(), 1);
        tx.commit();
    }
}

So what would the InfiniSpan test look like? Glad you asked. Just like the base test does, it mirrors the JPA version very closely:

package chapter12.hibernate;

import chapter12.Person;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.ogm.datastore.infinispan.Infinispan;
import org.hibernate.query.Query;
import org.testng.annotations.BeforeMethod;


public class InfinispanTest extends BaseHibernateOGMTest {
    @Override String getConfigurationName() {
        return Infinispan.DATASTORE_PROVIDER_NAME;
    }


    @BeforeMethod
    public void clearInfinispan() {
        try (Session session = getSession()) {
            Transaction tx = session.beginTransaction();
            Query<Person> query=session.createQuery("from Person", Person.class);
            for(Person p:query.list()) {
                session.delete(p);
            }
            tx.commit();
        }
    }
}

Summary

Hibernate OGM is a library that offers a bridge from Hibernate to nonrelational datastores, allowing easy and convenient usage of data grids or other document storage mechanisms. The code can be unusual compared to idiomatic Java persistence code, largely because of impedance mismatches between storage models, but such mismatches are fairly rare and easy to address.

Footnotes

1 Hibernate OGM’s home page is http://hibernate.org/ogm/ .

2 This is not a good idea. Sixty-four thousand columns, sure. Two billion is a bit much.

3 Many caches are now billing themselves as forms of NoSQL. Whether this is true or not tends to depend on how persistent the cache really is.

4 The Java Content Repository (JCR) can manage both; nodes in JCR can have an enforced schema or be freeform. Most adherents strongly advocate the freeform aspect.

5 The shrieks of horror you’re hearing are from all the advocates of good design, wondering how one can ever create an application where objects aren’t designed, but just happen.

6 This is by far the most important sentence in this chapter, in terms of how powerful Hibernate can be.

7 Of course, there are also mismatches between the relational model and object design, which is part of what makes Hibernate so useful – it addresses most of those mismatches pretty well.

8 It’s possibly a bit unfair to say that there’s no generalized solution; there may be.

9 When writing this book, a version mismatch between Hibernate OGM and Hibernate’s core was the source of a rather frustrating bug. Using the correct (and matched) versions cleared the problem up.

10 Lucene’s website is https://lucene.apache.org/ .

11 From a runtime perspective, we would notice; PostgreSQL is very fast, but an in-memory datagrid like Infinispan will typically run circles around a relational database for all supported operations like reads and writes. PostgreSQL will be better at data warehousing and aggregation, speaking very generally.

12 We’ll see this again when we look at the Hibernate Search facility.

13 In a fuzzy search, it’s possible to get a hit on “gives” with “give” as a search term, and vice versa. Verbatim searches wouldn’t return a hit on “give” with “gives.”

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset