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 jakarta.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 java 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 java type a TypeContributor is contributed. There are some default types (for example binary or datetime) that are always registered, but other modules can contribute java types as well (see Custom java 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. ValidationInterceptor runs the entity validation before the changes are flushed to the database

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.

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.

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

To reduce memory consumption a custom BatchLoaderFactory (CustomBatchLoaderFactory is contributed. As we have custom AbstractCollectionPersister the CollectionLoader are probably never used. Therefore we don’t initialize them until necessary, as they take up a lot of memory.