Categories
Guides/HowTo

Integrating Hibernate 5 into a Java 8 application w/ Hikari CP.

I recently improved the database integration in the JeakBot-Framework using API interfaces for plugins to access different parts of the JPA.

Fair warning: You should check “Sec. 7.5 of the Hibernate ORM User Guide: Using HikariCP” first and use that, if that fits your needs.


I recently improved the database integration in the JeakBot-Framework using API interfaces for plugins to access different parts of the JPA.

For this, I wanted to have Hibernate available in the API and at the same time, provide the same persistence units using DataSources. Preferably with only one representation of persistence units and no “hacky” way of retrieving instances based on casted implementations.
As most results online tell users to just cast the Hibernate classes to their implementations, use unwrap or do other (in my opinion) convention-breaking stuff, I had a hard time finding a solution that I like.
But eventually, I found a very convenient way of doing this:
Bootstrapping Hibernate with HikariCP manually.

Here’s how I did it.


Preparations & Representation

First, I created some sort of PersistenceUnit representation class & api which will make the different configuration values available to others:

package de.fearnixx.jeak.service.database;

import javax.persistence.EntityManager;
import javax.sql.DataSource;

public interface IPersistenceUnit {

    String getUnitId();
    String getJdbcUrl();
    String getHost();
    String getPort();
    String getSchemaName();
    String getUsername();
    String getPassword();
    String getDriver();

    DataSource getDataSource();
    EntityManager getEntityManager();
}

(I’ve omitted spacing and JavaDoc as this is enough to get the drill. You can view the source on GitLab.)

Afterwards, I created the implementation class which will be responsible for initializing Hikari and finishing Hibernate bootstrapping using an initialize method.

public class HHPersistenceUnit extends Configurable implements IPersistenceUnit, AutoCloseable {
    private static final Logger logger = LoggerFactory.getLogger(HHPersistenceUnit.class);

    private final Map<String, String> dataSourceOpts = new HashMap<>();
    private final BootstrapServiceRegistry baseRegistry;
    private final String unitId;

    private boolean isClosed = false;
    private String jdbcUrl;
    private String host;
    private String port;
    private String schemaName;
    private String username;
    private String password;
    private String driver;

    private HikariDataSource hikariDS;
    private StandardServiceRegistry hibernateServiceRegistry;
    private SessionFactory hibernateSessionFactory;

    public HHPersistenceUnit(String unitId, BootstrapServiceRegistry baseRegistry) {
        this.unitId = unitId;
        this.baseRegistry = baseRegistry;
    }

    public boolean initialize() {
        if (hikariDS != null) {
            throw new IllegalStateException("Cannot re-initialize data source!");
        }

        if (loadConfig()) {
            readConfiguration();
            initializeHikariSource();
            return initializeHibernateServices();
        } else {
            return false;
        }
    }

    // === STEP ONE === //
    private void readConfiguration() {
        // Retrieve your configuration in some way.
    }

    // === STEP TWO === //
    private void initializeHikariSource() {
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setJdbcUrl(getJdbcUrl());
        hikariConfig.setUsername(getUsername());
        hikariConfig.setPassword(getPassword());

        dataSourceOpts.forEach(hikariConfig::addDataSourceProperty);
        hikariDS = new HikariDataSource(hikariConfig);
    }

    // === STEP THREE === //
    private boolean initializeHibernateServices() {
        try {
            StandardServiceRegistryBuilder registryBuilder = new StandardServiceRegistryBuilder(baseRegistry);
            registryBuilder.applySetting("hibernate.format_sql", "true");
            // Tell Hibernate to use Hikaris DataSource under the hood.
            registryBuilder.applySetting("hibernate.connection.datasource", hikariDS);

            hibernateServiceRegistry = registryBuilder.build();
            MetadataSources metaSources = new MetadataSources(hibernateServiceRegistry);
            for (Class<?> aClass : /* YOUR CLASSES HERE */) {
                metaSources.addAnnotatedClassName(aClass.getName());
            }
            hibernateSessionFactory = metaSources.getMetadataBuilder().build().buildSessionFactory();
            return true;
        } catch (HibernateException e) {
            logger.warn("Failed to initialize Hibernate for persistence unit: {}", unitId, e);
            return false;
        }
    }

    @Override
    public DataSource getDataSource() {
        return hikariDS;
    }

    @Override
    public EntityManager getEntityManager() {
        return hibernateSessionFactory.createEntityManager();
    }

    @Override
    public synchronized void close() throws Exception {
        if (isClosed) {
            throw new IOException("Persistence unit already closed!");
        }

        isClosed = true;
        hikariDS.close();
        hibernateSessionFactory.close();
        StandardServiceRegistryBuilder.destroy(hibernateServiceRegistry);
    }
}

(Again, I’ve omitted non-important stuff such as the getters. View the source here.)

Let me explain the workflow as I understood it and as shallow as is required just to get this running.

1. Read configuration value

The configuration is read to populate the data source settings (host, port, driver type, username, password, database name). You will have to choose your way on how to do that. The JeakBot-Framework uses my Confort library for this.

2. Initialize Hikari configuration & DS

Initializing Hikaris configuration and DataSource classes is pretty straight forward as it requires you only to set the URL in the form of jdbc:<driver>://<host>:<port>/<database-name> and username + password, in case you even need them.

Afterwards, you can let your administrators add custom datasource options by just passing them on to the configuration.

Lastly, construct a new HikariDataSource and pass the configuration to it. => That’s your DataSource implementation for low-level java.sql.Connection access.

3. Initialize Hibernate

Hibernate initialization is a little trickier, but when no customization is needed, this boils down to just a few more method and constructor calls.

First, we create a common base registry for all persistence units (which is then passed to their constructor).
The base registry is used to provide strategy selectors, class loaders and configure lookup precedence.

            BootstrapServiceRegistryBuilder baseRegistryBuilder = new BootstrapServiceRegistryBuilder();
            this.baseRegistry = baseRegistryBuilder.build();

Secondly and as the first part in the persistence unit class, we create a service registry for the current persistence unit which is based on the base registry.

Here, we provide the HikariDataSource to Hibernate and can also optionally set some other unit-specific values.

Now, Hibernate requires information about the entity classes available. This is done by creating a MetadataSources instance and passing the created registry to it. Additionally, we programmatically tell the metadata sources what entity classes we know of and want to be available to this persistence unit. (The JeakBot-Framework uses org.Reflections for this.)

Finally, we can create the session factory from the metadata sources which will be able to create instances of EntityManager for us.

4. Cleaning up afterwards

I made the persistence unit implement AutoClosable just so it complies with conventions and implemented the close method.

In that method, we close the HikaryDataSource (which will close its internal connection pool), close the session factory (which will clear Hibernate session caches) and destroy the service registry (which will stop services registered to it).

It appears that this is enough as I did not run into dead-threads issues upon application exit.


Thank you

for reading my first “guide” on this blog.

I sincerely hope this will help some other programmers integrating Hibernate and/or Hikari into their applications and allow them to let API users choose which level of abstraction is the best for their code.

If you have any questions, corrections and/or additions regarding this post, feel free to get in touch.

Best regards,

– Mark


Sources / Further reading

By Mark

See my introductory post: https://m-lessmann.de/?p=16

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.