Redouble AI Nucleo documentation
Part 7 · Hundreds of agents in one JVM

Hibernate sessions

ai.redouble.nucleo.hibernate · nucleo-hibernate

For a Hibernate application, this module gives a tool a Hibernate Session of its own for exactly as long as the tool runs. The session is opened from the application's own SessionFactory when the job is admitted, holds one database connection from then until the job ends, and is closed when the job ends, whether it succeeded, failed or timed out. The tool never opens or closes it.

HibernateResourceProvider is a database provider in the sense of Admission: the application registers it once under a name with a limit, and admission lets at most that many jobs hold one of its sessions at a time. For a plain DataSource the same role is played by the JDBC provider.

Registering the provider

The application registers the provider once at startup, with the name jobs will look it up by, its SessionFactory, how many jobs may hold a session at once, and how long a commit may take to complete (below):

HibernateResourceProvider db = new HibernateResourceProvider("default", sessionFactory, 20, Duration.ofSeconds(30));
DBResourceProviders.register(db);

The pool behind the factory stays the application's, and the provider never reads or tunes it. The limit is yours to state, and it must not exceed the connections the pool can spare for jobs. It shows up in the health snapshot as db:<name>.

Using it in a tool

The tool looks the provider up by name, declares it, and takes its session in execute:

// in a job
HibernateResourceProvider db = DBResourceProviders.get("default", HibernateResourceProvider.class);
req.addProvider(db);
req.setRequiresTransaction(true);
...
Session session = resources.get(db);

With setRequiresTransaction(true) Nucleo begins a transaction before execute, commits it after, and rolls it back when execute throws. A job that declares setReadOnly(true) gets a session whose entities are read-only by default, on a connection marked read-only.

Why the session keeps its connection

Left to its default, a resource-local Hibernate session, one whose transactions are plain JDBC transactions, hands its connection back to the pool after every transaction and takes a new one for the next. For a job that would mean going back to the pool, and possibly waiting there, at every commit, while it holds its admission permit. So the provider opens each session in PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_HOLD and takes the connection out at once. The mode is set on that session alone: the application's factory, and every session other code opens from it, stay as they are.

What happens at the end

Closing or aborting twice does nothing the second time, and a handle made by another provider is refused with IllegalArgumentException. HibernateResourceProviderTest holds each of these rules, together with the contract every provider passes (DBResourceProviderContract in nucleo-core's test-jar).

A data layer with its own session wrapper

An application that wraps Hibernate's session in a type of its own can hand jobs that type. AbstractHibernateResourceProvider does everything above, and a subclass says what a job unwraps: expose(session) builds the job's object around the session, and closing(value) runs the application's own end-of-work bookkeeping on it before the session closes (by default nothing). HibernateResourceProvider is the subclass that exposes the Session itself.

The base class receives the factory as a supplier and asks for it at every acquire, so an application that creates its providers before its persistence layer is up, as a web application may, is served the factory that is current when each job starts.

^