Example of Using Java Hibernate Driver

Player Example

In these simple examples we will map the Players table from the Hockey sample data provided with the product (see NUODB_HOME/samples/quickstart/sql).

In each case the first player in the table, with id aaltoan01, is fetched.

The application is deliberately very minimal. The intent is to show the configuration required to use NuoDB, nothing more.

Here is the class Player, annotated so Hibernate/JPA knows how to map it to/from the underlying table. An annotated class requires just two annotations. It must be marked as an entity and its key (id) must be defined. The additional @Table annotation overrides the default mapping (to a table of the same name, Player), mapping it to Players instead.

To reduce clutter all the getters and setters have been omitted.

package com.nuodb.docs;

@Entity
@Table(name = "Players")
public class Player {
    @Id
    private String playerId;

    private String firstName;
    private String lastName;
    private int height;
    private int weight;
    private int firstNHL;
    private int lastNHL;

    private String position;
    private int birthYear;
    private int birthMon;
    private int birthDay;
    private String birthCountry;
    private String birthState;
    private String birthCity;

    public Player() {
        /* Hibernate/JPA require a default constructor */
    }

    /* Getters and setters omitted ... */
}

Using JPA with Hibernate as the Provider

The Java Persistence API (JPA) is configured by the file META-INF/persistence.xml which must be in the class path. For a default maven configuration, this file would be in src/java/resources/META-INF. It defines one (or more) persistence units, one for each database in use.

In this file you declare:

  • The NuoDB driver class, connection URL, username and password using JPA properties

  • Additional provider-specific properties are supported, in this case hibernate properties. These include the NuoDB dialect and the default schema. A few other recommended/useful properties are also shown here.

  • Note the persistence unit is called Hockey

  • The XML below deliberately avoids the differences between JPA 2 and 3.

    • The properties prefixed xxx.persistence would be prefixed javax.persistence in JPA 2 and jakarta.persistence in JPA 3.

    • See here for a complete JPA 2 and JPA 3 examples of persistence.xml.

<?xml version="1.0" encoding="UTF-8" ?>

<!-- The configuration file for JPA -->
<persistence ...>  <!-- namespace and schema declaration depend on JPA version -->

    <!-- Each persistence unit correspond to a database -->
    <persistence-unit name="Hockey" transaction-type="RESOURCE_LOCAL">
        <class>com.nuodb.docs.Player</class>
        <properties>
            <!-- JPA Properties - 'xxx' = 'javax' (JPA 2) or 'jakarta' (JPA 3) -->
            <property name="xxx.persistence.jdbc.driver"
                      value="com.nuodb.hibernate.NuoHibernateDriver" />
            <property name="xxx.persistence.jdbc.url"
                      value="jdbc:com.nuodb.hib://localhost/test" />
            <property name="xxx.persistence.jdbc.user" value="dba" />
            <property name="xxx.persistence.jdbc.password" value="goalie" />

            <!-- HIBERNATE Properties -->
            <property name="hibernate.connection.schema" value="Hockey" />
            <property name="hibernate.dialect"
                      value="com.nuodb.hibernate.NuoDBDialect" />

            <!-- Print SQL to stdout, format it nicely -->
            <property name="hibernate.show_sql" value="true" />
            <property name="hibernate.format_sql" value="true" />
            <property name="hibernate.use_sql_comments" value="true" />

            <!-- Check schema matches our classes -->
            <property name="hibernate.hbm2ddl.auto" value="validate" />
        </properties>

    </persistence-unit>
</persistence>

The code that fetches the Player uses an EntityManager to do all the hard work. An entity manager corresponds to a Hibernate Session and provides transaction semantics (not used here). Both the entity manager and the factory must be closed after use, but unfortunately in JPA 2 (Hibernate 5) neither implements AutoCloseable; instead a try …​ finally block is required.

EntityManagerFactory factory = null;
EntityManager em = null;

try {
    factory = Persistence.createEntityManagerFactory("Hockey");
    em = factory.createEntityManager();

    String id = "aaltoan01";
    TypedQuery<Player> q = em.createQuery("SELECT p FROM Player p WHERE p.id = :id", Player.class);
    q.setParameter("id", id);
    Player p1 = q.getSingleResult();

    System.out.println("Found: " + p1.firstName + ' ' + p1.lastName);
    System.out.println("       height: " + p1.height + ", weight: " + p1.weight + "lb");
    System.out.println("       active: " + p1.firstNHL + '-' + p1.lastNHL);
    System.out.println(String.format("       born: %4d-%02d-%02d in %s, %s", p1.birthYear, p1.birthMon,
           p1.birthDay, p1.birthCity, p1.birthCountry));
} finally {
    if (em != null)
        em.close();
    if (factory != null)
        factory.close();
}

Using Spring JPA and Spring Boot

Spring supports a concept known as Separation of Concerns, typically separating application setup and configuration from the business logic that is actually important. Configuration is isolated in dedicated classes (or XML definitions). The objects defined and created by the configuration are known as Spring Beans (a nod to Java Beans). Spring JPA simplifies the configuration required to use JPA (and, if using Hibernate as a provider, eliminates the persistence.xml entirely).

Spring Boot goes a step further and, given some configuration properties sets up default, obvious Spring configurations for you. Inside Spring Boot there is a default JPA setup, typical of how most applications would use JPA and Spring. All you have to do is fill in the blanks to make it work (via properties in Spring Boot’s application.properties configuration file). Spring Boot is fundamentally a programmable Spring Bean generator.

Here is what application.properties looks like in our case. The many, many properties that can be used to control Spring Boot are defined in the Common Application Properties section of the Spring Boot documentation.

Spring supports Hibernate 6 from v6 of the Spring Framework and v3 of Spring Boot. Both require at least Java 17, even though Hibernate 6 only requires Java 11 (or later).
# FILE: application.properties
#
# DATASOURCE PROPERTIES

# Default DataSource connection properties
spring.datasource.driver-class-name=com.nuodb.jdbc.Driver
spring.datasource.url=jdbc:com.nuodb://localhost/test
spring.datasource.username=dba
spring.datasource.password=goalie
spring.datasource.platform=nuodb

# Schema to use
spring.datasource.hikari.schema=Hockey


# HIBERNATE PROPERTIES

# Don't let Hibernate generate the Players table automatically, it already exists
spring.jpa.hibernate.ddl-auto=none

# Use NuoDB's dialect to generate NuoDB specific SQL
spring.jpa.database-platform=com.nuodb.hibernate.NuoDBDialect

# Enable logging of SQL statements
spring.jpa.show-sql=true

# Lay the SQL out nicely across multiple lines
spring.jpa.properties.hibernate.format_sql=true

# Spring Boot enables CamelCase to SnakeCase mapping by default (for example:
# playerId maps to PLAYER_ID column). This turns that mapping off and maps
# field names to column names unchanged
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

# If you want to see what Spring Boot does
#debug=true

The application looks like this:

/* Look for Entities (Player class) in this package (and any sub-packages). */
@EntityScan("com.nuodb.docs")
/* Enable transactions, Spring JPA creates an EntityManager to implement txns. */
@EnableTransactionManagement(proxyTargetClass = true)
/* Where to look for Spring Beans - in this case nowhere else as everything is in this class. */
@SpringBootApplication(scanBasePackages = "no-such-package")
public class SpringBootClient {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootClient.class, args);
    }

    /**
     * Spring Boot recommended way to run code at startup.
     *  - This class is marked as an {@code @Component}, so Spring will automatically
     *    create an instance.
     *  - Because it implements {@code CommandLineRunner}, Spring Boot will automatically
     *    invoke its {@code run()} method after all configuration is complete.
     */
    @Component
    public class ClientRunner implements CommandLineRunner {

        /*
         * Spring will automatically create a context and set this data-member (even
         * though it is private). In fact, what it sets is a proxy - see below.
         */
        @PersistenceContext
        private EntityManager entityManager;

        @Override
        public void run(String... args) throws Exception {
            /* Find an existing Player and write details to the console */
            String id = "aaltoan01";
            TypedQuery<Player> q = entityManager.createQuery("SELECT p FROM Player p WHERE p.id = :id", Player.class);
            q.setParameter("id", id);
            Player p1 = q.getSingleResult();

            System.out.println("Found: " + p1.firstName + ' ' + p1.lastName);
            System.out.println("       height: " + p1.height + "\" weight: " + p1.weight + "lb");
            System.out.println("       active: " + p1.firstNHL + '-' + p1.lastNHL);
            System.out.println(String.format("       born: %4d-%02d-%02d in %s, %s", p1.birthYear, p1.birthMon,
                    p1.birthDay, p1.birthCity, p1.birthCountry));
        }

    }
}

The implementation of JPA with Spring is subtle.

  • An entity manager typically corresponds to a JDBC connection.

  • Expected JPA usage is to create a new instance of any class that accesses persistent data for each transaction in each thread.

    • Each instance gets given a different EntityManager and hence a different connection.

  • With Spring these data access classes are usually singletons, one per application (like ClientRunner above).

    • So the EntityManager that Spring injects is actually a proxy object.

    • When used, the proxy gets an EntityManager for the current thread to use (creating it if necessary) and delegates all methods to it.

    • Spring does this so that the JPA usage looks the same, whether you are using Spring or not.

Using Hibernate Sessions

This is no longer the preferred approach, using JPA with Hibernate as the provider is recommended instead. However, many applications still use Session API, so here is the application using just Hibernate sessions. The annotated Player class is still our one and only persistent class.

The configuration file for raw Hibernate is hibernate.cfg.xml. It looks like this:

<!DOCTYPE hibernate-configuration SYSTEM "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<!-- Configuration for pure Hibernate using Session API -->
<!-- The 'hibernate.' prefix to property names is optional -->
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">com.nuodb.hibernate.NuoHibernateDriver</property>
        <property name="hibernate.connection.url">jdbc:com.nuodb.hib://localhost/test</property>
        <property name="hibernate.connection.username">dba</property>
        <property name="hibernate.connection.password">goalie</property>

        <property name="hibernate.connection.schema">Hockey</property>
        <property name="hibernate.dialect">com.nuodb.hibernate.NuoDBDialect</property>

        <!-- Use the Hibernate built-in pool for tests. -->
        <property name="hibernate.connection.pool_size">1</property>

        <!-- Disable the second-level cache -->
        <property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
        <property name="cache.use_query_cache">false</property>
        <property name="cache.use_minimal_puts">false</property>

        <!-- In eager fetching, only join three tables deep if joins are used -->
        <property name="max_fetch_depth">3</property>

        <!-- Print SQL to stdout, format it nicely -->
        <property name="hibernate.show_sql">true</property>
        <property name="hibernate.format_sql">true</property>
        <property name="hibernate.use_sql_comments">true</property>

        <!-- Check schema matches our classes -->
        <property name="hibernate.hbm2ddl.auto">validate</property>

        <!-- Use thread-bound persistence context propagation, scoped to the transaction -->
        <property name="current_session_context_class">thread</property>

        <mapping class="com.nuodb.docs.Player" />

    </session-factory>
</hibernate-configuration>

Things to note:

  • The hibernate.connection.xxx properties define the NuoDB driver class, URL, username, password and default schema.

  • The NuoDB dialect is specified by the hibernate.dialect property

  • The Player class is marked as a persistent class (or entity) by the <mapping> element. Normally there is one element per entity class. Using Spring and entity scanning becomes increasingly attractive as the number of classes increases.

  • Spring has always offered first-class support for configuring Hibernate Session API but this is removed from Spring 6 and Spring Boot 3 in favor of JPA 3 entity managers.

Here is the application code (100% pure Hibernate, no JPA or Spring):

Configuration configuration = new Configuration();

configuration.configure();

try (SessionFactory factory = configuration.buildSessionFactory(); //
        Session session = factory.openSession();) {

    String id = "aaltoan01";
    Player p1 = session.find(Player.class, id);

    System.out.println("Found: " + p1.firstName + ' ' + p1.lastName);
    System.out.println("       height: " + p1.height + "\" weight: " + p1.weight + "lb");
    System.out.println("       active: " + p1.firstNHL + '-' + p1.lastNHL);
    System.out.println(String.format("       born: %4d-%02d-%02d in %s, %s", p1.birthYear, p1.birthMon,
            p1.birthDay, p1.birthCity, p1.birthCountry));
} // End of block closes both session and factory