Hibernate Initialisation without XML

Hibernate Initialisation without XML

While working on some R&D last weekend, I was surprised when every introduction to Hibernate started with creating a persistence.xml file.

Ultimately this is probably because many developers implicitly assume that ones JPA persistence unit will ultimately be managed by an application server. But for small containerised applications without application server, having multiple mechanisms to configure the application - i.e. persistence.xml and whatever native mechanism one prefers - is extremely unwieldy. As an example, I have seen some really convoluted approaches for referencing environment variables to pull in database credentials.

I was surprised that even web-searches like “Hibernate without persistence.xml” turned out quite unhelpful. So I finally ended up looking into integration tests of Hibernate to find out how this might work. And to top things off, it is extremely straightforward to do, too.

In this code, configuration is pulled from a Vert.x configuration object.

    lateinit var jdbcConfig: JdbcConfig

    // can't be `by lazy` because it needs to be `suspend`
    private suspend fun initializeDatabaseConfiguration() {
        if (Boot::jdbcConfig.isInitialized) {
            return
        }

        val config = configRetriever.config.await()
        val dbConfig = config.getJsonObject("database")
        val url = dbConfig.getString("url")
        val username = dbConfig.getString("username")
        val password = dbConfig.getString("password")

        jdbcConfig = JdbcConfig("jdbc:$url", username, password)
    }

Now that this information is there, Hibernate can be set up. Here I’m looking into Hibernate Reactive, so some custom configuration is required:

    private fun initializeJPA(pluginManager: PluginManager): SessionFactory {
        val configuration = Configuration()
        configuration
            .setProperty(
                Settings.JPA_PERSISTENCE_PROVIDER,
                "org.hibernate.reactive.provider.ReactivePersistenceProvider"
            )
            .setProperty(Settings.DIALECT, "org.hibernate.dialect.PostgreSQL10Dialect")
            .setProperty(Settings.URL, jdbcConfig.url)
            .setProperty(Settings.USER, jdbcConfig.username)
            .setProperty(Settings.PASS, jdbcConfig.password)

Next, one will need to add the entity classes to the configuration, which will probably work a bit differently for each application. In this case, I’m using PF4J and pulling in Hibernate entities from an ExtensionPoint.

        for (extension in pluginManager.getExtensions(HibernateConfiguration::class.java)) {
            for (clz in extension.annotatedClasses()) {
                configuration.addAnnotatedClass(clz)
            }
        }

Since PF4J creates separate ClassLoaders for each plugin, Hibernate needs to be made aware of these through a custom ClassLoaderService instance. After that, the configuration is ready to be used and the SessionFactory can be instantiated:

        val allClassLoaders = mutableListOf(this.javaClass.classLoader)
            .also { it.addAll(pluginManager.plugins.map { it.pluginClassLoader }) }
        val classLoaderService = ClassLoaderServiceImpl(allClassLoaders, TcclLookupPrecedence.BEFORE)
        val serviceRegistry = ReactiveServiceRegistryBuilder().addService(
            ClassLoaderService::class.java, classLoaderService
        )
            .applySettings(configuration.properties)
            .build()
        val jpaSessionFactory = configuration.buildSessionFactory(serviceRegistry)
        return jpaSessionFactory.unwrap(SessionFactory::class.java)
    }

That’s all for now. I’ll probably write a separate article about combining Vert.x, PF4J, Hibernate Reactive and Kodein soon.