Hibernate’s StatelessSession – What it is and how to use it
Take your skills to the next level!
The Persistence Hub is the place to be for every Java developer. It gives you access to all my premium video courses, 2 monthly Q&A calls, monthly coding challenges, a community of like-minded developers, and regular expert sessions.
Some of Hibernate’s core features are automatic dirty checks, flushes, and the 1st level cache. They make the implementation of most standard use cases simple and efficient. But they also add a lot of hidden complexity and are not a great fit for all use cases. Your typical nightly import job or most other use cases that perform lots of write operations don’t benefit from those features. They often even slow down such use cases. In those situations, Hibernate’s StatelessSession might be a better fit.
What is a StatelessSession?
The StatelessSession is a proprietary Hibernate feature that provides a command-oriented API that’s much closer to JDBC. I will show you a few examples of how to use it to implement typical read and write operations in this article. But before we take a closer look at the StatelessSession interface, we need to talk about the conceptual differences from a standard Session interface.
Hibernate’s StatelessSession doesn’t provide a 1st level cache, automated dirty checks, or write-behind automation. It also doesn’t provide lazy loading for your managed associations and doesn’t use the 2nd level or query cache. Any operation performed via a StatelessSession also doesn’t trigger any lifecycle events or interceptors.
Instead of all the automatic features that a regular Session or JPA’s EntityManager provides, the StatelessSession puts you in full control of the executed SQL statements. If you want to fetch some data from the database or initialize an association, you need to write and execute a query for it. And if you create a new or change an existing entity object, you need to call the insert, update or delete method on the StatelessSession interface to persist your change.
That requires you to put more thought into the technical side of your persistence layer. But if your use case doesn’t require automatic dirty checks, lazy loading, or 1st level caches, using a StatelessSession also drastically reduces Hibernate’s performance overhead. That makes Hibernate’s StatelessSession a great fit for use cases that import or update a huge set of data. That’s especially the case if you’re already using Hibernate for other parts of your application and want to reuse your entity model.
You could also try the StatelessSession if you need to fetch many entity objects that you will not change and if you don’t require any lazy fetching of associated entities. But queries that return use case specific DTO projections are often a better fit for those use cases.
How to use a StatelessSession?
Let’s use a StatelessSession for reading and writing entity objects. You can get a StatelessSession instance in a similar way as the normal Session instance. If your application runs in an application server or is based on Spring, you can simply inject a StatelessSession instance. And if you’re using plain Hibernate, you can call the openStatelessSession method on your SessionFactory and use it to start a transaction.
StatelessSession statelessSession = sf.openStatelessSession(); statelessSession.getTransaction().begin(); // do something statelessSession.getTransaction().commit();
After you get a StatelessSession instance, you can use it to read and write data.
Inserting and updating entities using a StatelessSession
Most projects use a StatelessSession to insert or update huge data sets. So let’s start with 2 simple write operations.
The most important methods you need to know when implementing write operations using a StatelessSession are the methods insert, update and delete. You need to call them if you want to persist a new entity object or update or delete an existing one. When doing that, please be aware that a StatelessSession doesn’t support cascading. So, you need to trigger your write operations for every entity object you want to persist.
In the following test case, I want to insert a new ChessPlayer entity object and fix a typo in the firstName afterward.
StatelessSession statelessSession = sf.openStatelessSession(); statelessSession.getTransaction().begin(); ChessPlayer player = new ChessPlayer(); player.setFirstName("Torben"); player.setLastName("Janssen"); log.info("Perform insert operation"); statelessSession.insert(player); log.info("Update firstName"); player.setFirstName("Thorben"); statelessSession.update(player); statelessSession.getTransaction().commit();
You probably already recognized the main differences if you’re familiar with Hibernate’s Session interface or JPA’s EntityManager. I called the insert method to persist the new ChessPlayer object and the update method to persist the changed firstName.
As I mentioned earlier, a StatelessSession doesn’t provide a 1st level cache, dirty checks, and automatic write-behind optimizations. Due to that, Hibernate immediately performs an SQL INSERT statement when you call the insert method with an entity object. And Hibernate doesn’t detect the changed firstName attribute. You need to call the update method if you want to persist that change. Hibernate then immediately executes an SQL UPDATE statement.
If you use my recommended logging configuration for development systems, you can see all of that in the log output.
17:46:23,963 INFO [com.thorben.janssen.TestStatelessSession] - Perform insert operation 17:46:23,968 DEBUG [org.hibernate.SQL] - select nextval('player_seq') 17:46:23,983 DEBUG [org.hibernate.SQL] - insert into ChessPlayer (birthDate, firstName, lastName, version, id) values (?, ?, ?, ?, ?) 17:46:23,988 INFO [com.thorben.janssen.TestStatelessSession] - Update firstName 17:46:23,989 DEBUG [org.hibernate.SQL] - update ChessPlayer set birthDate=?, firstName=?, lastName=?, version=? where id=? and version=?
As you can see in this example, not having a 1st level cache, automatic dirty checks, and flush operations, requires you to trigger all database interactions. This puts you in full control of the execution of the SQL statements, and it provides better performance when writing huge datasets.
Reading entity objects using a StatelessSession
When you’re using a StatelessSession to read entity objects from the database, your code looks identical to the one using a standard Session. But there are a few important Hibernate-internal differences you need to know.
I mentioned earlier that a StatelessSession doesn’t provide lazy loading. Due to that, you need to initialize all the required associations when fetching an entity object from the database. Otherwise, Hibernate throws a LazyInitializationException when you access the association for the 1st time. The best way to initialize an association is to use an EntityGraph or include a JOIN FETCH clause in your JPQL query.
In the following examples, I use a JPQL query with 2 JOIN FETCH clauses to load a ChessPlayer entity object. The JOIN FETCH clauses tell Hibernate to initialize the association to the games they played with the white and black pieces.
StatelessSession statelessSession = sf.openStatelessSession(); statelessSession.getTransaction().begin(); ChessPlayer player = statelessSession.createQuery(""" SELECT p FROM ChessPlayer p JOIN FETCH p.gamesWhite JOIN FETCH p.gamesBlack WHERE p.id=:id""", ChessPlayer.class) .setParameter("id", 1L) .getSingleResult(); log.info(player.getFirstName() + " " + player.getLastName()); log.info("White pieces: " + player.getGamesWhite().size()); log.info("Black pieces: " + player.getGamesBlack().size()); statelessSession.getTransaction().commit();
As mentioned earlier, the differences between a read operation implemented using a StatelessSession, and a Session instance is not directly visible in your code. And the same is true for the log output.
17:58:09,648 DEBUG [org.hibernate.SQL] - select c1_0.id, c1_0.birthDate, c1_0.firstName, g2_0.playerBlack_id, g2_0.id, g2_0.chessTournament_id, g2_0.date, g2_0.playerWhite_id, g2_0.round, g2_0.version, g1_0.playerWhite_id, g1_0.id, g1_0.chessTournament_id, g1_0.date, g1_0.playerBlack_id, g1_0.round, g1_0.version, c1_0.lastName, c1_0.version from ChessPlayer c1_0 join ChessGame g1_0 on c1_0.id=g1_0.playerWhite_id join ChessGame g2_0 on c1_0.id=g2_0.playerBlack_id where c1_0.id=? 17:58:09,682 DEBUG [org.hibernate.stat.internal.StatisticsImpl] - HHH000117: HQL: SELECT p FROM ChessPlayer p JOIN FETCH p.gamesWhite JOIN FETCH p.gamesBlack WHERE p.id=:id, time: 56ms, rows: 1 17:58:09,685 INFO [com.thorben.janssen.TestStatelessSession] - Magnus Carlsen 17:58:09,685 INFO [com.thorben.janssen.TestStatelessSession] - White pieces: 1 17:58:09,686 INFO [com.thorben.janssen.TestStatelessSession] - Black pieces: 2
But there are important internal differences. Hibernate not only doesn’t support lazy loading for StatelessSessions but also doesn’t use any caches, including the 1st level cache. That reduces the overhead performed for each database query. But Hibernate can no longer guarantee that you always get the same object if you’re reading the same entity multiple times within the same session.
You can see that in the following test case, in which I execute the same query twice.
StatelessSession statelessSession = sf.openStatelessSession(); statelessSession.getTransaction().begin(); ChessPlayer player1 = statelessSession.createQuery(""" SELECT p FROM ChessPlayer p JOIN FETCH p.gamesWhite JOIN FETCH p.gamesBlack WHERE p.id=:id""", ChessPlayer.class) .setParameter("id", 1L) .getSingleResult(); ChessPlayer player2 = statelessSession.createQuery(""" SELECT p FROM ChessPlayer p JOIN FETCH p.gamesWhite JOIN FETCH p.gamesBlack WHERE p.id=:id""", ChessPlayer.class) .setParameter("id", 1L) .getSingleResult(); assertNotEquals(player1, player2); statelessSession.getTransaction().commit();
Using a standard Session instance, Hibernate would execute the 1st query, instantiate an entity object for the returned record and store it in the 1st level cache. After that, it would execute the 2nd query, check the 1st level cache for an entity object that represents the record returned in the result set, and return that object. That ensures that you always get the same entity object if you fetch a database record multiple times within the same session.
Without the 1st level cache, the StatelessSession doesn’t know about any previously selected entity objects. It has to instantiate a new object for every record returned by a query. Due to that, you can get multiple objects that represent the same database record. In the previous example, that’s the case for the player1 and player2 objects.
Please keep that in mind when writing your business code and make sure that you always use the same entity object for your write operations. Otherwise, you might overwrite previously performed changes.
Hibernate’s StatelessSession interface provides a command-oriented API that gives you more control over the executed SQL statements. It’s much closer to JDBC and doesn’t support any caches, automatic flushes, dirty checks, cascading and lazy loading.
That makes a StatelessSession a great fit for all use cases that don’t benefit from these features. Typical examples are batch jobs or other use cases that perform many simple write operations.
But without all those features, implementing your persistence layer requires a little more work. You need to trigger all database operations yourself. E.g., after you change one or more entity attributes, you need to call the update method on your StatelessSession instance to persist the change in the database. Otherwise, Hibernate will not be aware of the change and will not trigger any SQL statements.
You also need to initialize all required associations when you fetch an entity object from the database. And you need to be aware that a StatelessSession doesn’t return the same entity object if you fetch the same record multiple times. That makes the handling of query results a little more complex.
Overall, Hibernate’s StatelessSession is a great feature if you want to reduce the overhead of Hibernate’s Session handling and don’t need features like lazy loading, cascading, a 1st level cache, and automatic flushes.