This is an internal documentation. There is a good chance you’re looking for something else. See Disclaimer.

Hibernate Setup

Introduction

Hibernate is an implementation of the JPA specification, however we use the Hibernate API directly in many cases (instead of using the JPA API in the javax.persistence package), mostly because we need many custom Hibernate features to be able to provide exactly the same behaviour as the old Persistence API did.

Build the SessionFactory

The first step is to initialize a SessionFactory. This is done by the SessionFactoryProvider, which is a spring @Configuration class.

The Hibernate bootstrapping process is documented here.

The SessionFactory should only be used internally by the persist/core module. If other modules would use it directly, they could bypass the security layer.

Participate in the bootstrap process

It is possible for other modules to apply custom configuration options during the building of the session factory by contributing a HibernateBootstrapContribution. This contribution provides several methods to participate in various steps of the bootstrapping process. It’s also possible to provide a priority to control the execution order of the different contributions. This can for example be used for registering custom user types.

The main configuration is done by HibernateCoreBootstrapContribution.

ContributionClassLoaderService

The ContributionClassLoaderService is a custom ClassLoaderService which makes it easy to contribute services at runtime and to avoid having to use the ServiceLoader API used by the default implementation.

Bootstrap steps

Register custom user extensions

Several user extensions are registered with the ContributionClassLoaderService:

  • For each custom user type a TypeContributor is contributed. There are some default types (for example binary or datetime) that are always registered, but other modules can contribute user types as well (see Custom user types).

  • FieldGenerator contributions (fields that are set automatically by the framework, like the create/update timestamps and users) (see Automatically generated values).

Generate entity classes

Entity classes are generated based on the entity models and then registered with the provided MetadataSources.

See Entity class generation.

Apply Hibernate properties

The next step is to apply the Hibernate configuration settings. The interface HibernatePropertiesProvider defines some common properties in a default method.

The only implementation (HibernatePropertiesProviderImpl) adds the connection options to the default properties. These are read from the application properties. The properties need to be transformed to a different format as Hibernate uses different options than HikariCP.

The ToccoDialectResolver is a custom DialectResolver, which makes sure that our custom dialects are used by hibernate. It is configured using the hibernate.dialect_resolvers property.

Injecting service factories

We use a custom implementation of PersisterFactory. This allows (manually) injecting services or contributions into a custom persister. Without using a custom factory, Hibernate just calls the default constructor.

Hibernate interceptor

A custom Hibernate Interceptor is registered as well. In order to be able to split up the functionality of the interceptor into different classes (perhaps from different modules) the DelegatingHibernateInterceptor is used (as it is not possible to register multiple interceptors). This class then delegates the events to the actual interceptor implementations.

Currently only one interceptor is used:

JDBC function registration

All JdbcFunction are registered with the SessionFactoryBuilder.

Event listener registration

Multiple Hibernate listeners (see EventType) are registered:

Startup time improvements

Hibernate completely initializes every entity during the construction of the session factory. Among many other things this includes:

  • A ProxyFactory for every entity (required to instantiate lazily loaded entity proxies). These are currently based on byte buddy and take some time to initialize, especially for hundreds of entities.

  • Several UniqueEntityLoader per entity (one per LockMode). Apart from the fact that we don’t need all lock modes, they are also expensive to initialize because they contain the SQL string required to load the entity.

This makes sense for a production environment, but during development a quicker startup time is more important because usually only a fraction of all entities is used. It therefore makes more sense to initialize these objects on the fly when they are needed for the first time.

To support this we use the CustomEntityPersister that returns a custom lazy implementation of UniqueEntityLoader which is not initialized until it is needed.

Similarly, the CustomEntityTuplizer does not initialize the ProxyFactory until it is needed.