Hibernate StatelessSession JDBC Batching
Are you struggling with performance issues in your Spring, Jakarta EE, or Java EE application?
What if there were a tool that could automatically detect what caused performance issues in your JPA and Hibernate data access layer?
Wouldn’t it be awesome to have such a tool to watch your application and prevent performance issues during development, long before they affect production systems?
Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, Micronaut, or Play Framework.
So, rather than fixing performance issues in your production system on a Saturday night, you are better off using Hypersistence Optimizer to help you prevent those issues so that you can spend your time on the things that you love!
Introduction
In this article, we are going to see how we can use the Hibernate StatelessSession in order to enable JDBC Batching for INSERT, UPDATE, and DELETE statements.
While the StatelessSession has been available for more than 20 years, until Hibernate 6, its usefulness was rather limited since it lacked support for batching, as well as other cool features, such as UPSERT.
Hibernate StatelessSession
Just like the Hibernate Session, you can create a Hibernate StatelessSession from the Hibernate SessionFactory:
StatelessSession session = sessionFactory.withStatelessOptions()
.openStatelessSession();
However, unlike the Hibernate Session or JPA EntityManager, the Hibernate StatelessSession doesn’t have an associated Persistence Context. For this reason, there is no First-Level Cache, and for this reason, entity operation cascading or dirty checking are not going to work when you are using the StatelessSession.
To simplify our integration tests that will demonstrate the Hibernate StatelessSession support for JDBC Batching, I’m going to create the following doInStatelessSession method that executes a Java Lambda in the context of a newly-created StatelessSession and JPA transaction:
protected void doInStatelessSession(
HibernateStatelessTransactionConsumer callable) {
StatelessSession session = null;
Transaction transaction = null;
try {
session = sessionFactory()
.withStatelessOptions()
.openStatelessSession();
transaction = session.beginTransaction();
callable.accept(session);
if (!transaction.getRollbackOnly()) {
transaction.commit();
} else {
try {
transaction.rollback();
} catch (Exception e) {
LOGGER.error("Rollback failure", e);
}
}
} catch (Throwable t) {
if (transaction != null && transaction.isActive()) {
try {
transaction.rollback();
} catch (Exception e) {
LOGGER.error("Rollback failure", e);
}
}
throw t;
} finally {
if (session != null) {
session.close();
}
}
}
Hibernate StatelessSession with Hibernate 7
Hibernate 7 introduce several utility methods that allow us to insert, update, or delete multiple records in a very efficient manner.
Batching INSERT statements with the Hibernate 7 insertMultiple
To insert 3 Post and PostComment entities, we can use the insertMultiple method that was added to the StatelessSession since version 7:
doInStatelessSession(session -> {
List<Post> posts = LongStream.rangeClosed(1, POST_COUNT).boxed()
.map(i -> new Post()
.setId(i)
.setTitle(String.format("Post no. %d", i)))
.toList();
final int COMMENT_COUNT = 5;
final List<PostComment> postComments = posts.stream()
.flatMap(post ->
LongStream.rangeClosed(1, COMMENT_COUNT).boxed()
.map(i -> new PostComment()
.setPost(post)
.setReview(
String.format(
"Post comment no. %d",
(post.getId() - 1) * COMMENT_COUNT + i
)
))
).toList();
session.insertMultiple(posts);
session.insertMultiple(postComments);
});
Hibernate is going to execute the following SQL INSERT statements:
Query:["
insert into post (title,id) values (?,?)
"], Params:[
(Post no. 1, 1),
(Post no. 2, 2),
(Post no. 3, 3)
]
Query:["select nextval('seq_post_comment')"], Params:[()]
Query:["
insert into post_comment (post_id,review,id) values (?,?,?)
"],
Params:[
(1, Post comment no. 1, 1),
(1, Post comment no. 2, 2),
(1, Post comment no. 3, 3),
(1, Post comment no. 4, 4),
(1, Post comment no. 5, 5),
(2, Post comment no. 6, 6),
(2, Post comment no. 7, 7),
(2, Post comment no. 8, 8),
(2, Post comment no. 9, 9),
(2, Post comment no. 10, 10),
(3, Post comment no. 11, 11),
(3, Post comment no. 12, 12),
(3, Post comment no. 13, 13),
(3, Post comment no. 14, 14),
(3, Post comment no. 15, 15)
]
Notice that even without enabling JDBC batching, the insertMultiple method was able to batch the INSERT statements, therefore reducing the number of database calls.
Batching UPDATE statements with the Hibernate 7 updateMultiple
Now, let’s assume we have to update several Post entities that were modified in their detached state:
List<Post> posts = doInJPA(entityManager -> {
return entityManager.createQuery("""
select p
from Post p
""", Post.class)
.setMaxResults(POST_COUNT)
.getResultList();
});
posts.forEach(post ->
post.setTitle(post.getTitle().replaceAll("no", "nr"))
);
LOGGER.info("Update Post entities");
doInStatelessSession(session -> {
session.updateMultiple(posts);
});
When executing the updateMultiple method call, we can see that Hibernate executes a single batch UPDATE statement:
Query:["
update post set title=? where id=?
"],
Params:[
(Post nr. 1, 1),
(Post nr. 2, 2),
(Post nr. 3, 3)
]
Just like insertMultiple method, the updateMultiple enables JDBC batch updates even if the Hibernate hibernate.jdbc.batch_size configuration property was not set.
Batching DELETE statements with the Hibernate 7 deleteMultiple
The Hibernate StatelessSession provides a deleteMultiple that we can use to delete the parent post entities along with their associated post_comment entries, as illustrated by the following example:
List<Post> posts = doInJPA(entityManager -> {
return entityManager.createQuery("""
select p
from Post p
join fetch p.comments
""", Post.class)
.setMaxResults(POST_COUNT)
.getResultList();
});
doInStatelessSession(session -> {
session.deleteMultiple(
posts.stream()
.flatMap(
post -> post.getComments().stream()
)
.toList()
);
session.deleteMultiple(posts);
});
When running the above integration test, Hibernate is going to execute the following DELETE statements:
Query:["
delete from post_comment where id=?
"],
Params:[
(1), (2), (3), (4), (5), (6), (7), (8),
(9), (10), (11), (12), (13), (14), (15)
]
Query:["
delete from post_comment where post_id in (?,?,?)
"],
Params:[
(1, 2, 3)
]
Query:[
"delete from post where id=?
"], Params:[
(1),
(2),
(3)
]
The first SQL statement removes the associated post_comment table records, and the second one deletes the post entries.
That’s it!
Hibernate StatelessSession with Hibernate 6 and 5
Just like typical JPA EntityManager or Hibernate Session, which simply extends the JPA EntityManager, the Hibernate StatelessSession can take advantage of the automatic JDBC batching feature of Hibernate that you can enable via the following settings:
properties.put(AvailableSettings.STATEMENT_BATCH_SIZE, "50");
Batching INSERT statements with the StatelessSession and Hibernate 6 and 5
Now, when inserting 3 Post entities using the doInStatelessSession utility method:
doInStatelessSession(session -> {
for (long i = 1; i <= POST_COUNT; i++) {
session.insert(
new Post()
.setId(i)
.setTitle(String.format("Post no. %d", i))
);
}
});
Hibernate is going to execute the following SQL INSERT statements:
Query:["
insert into post (title,id) values (?,?)
"],
Params:[
(Post no. 1, 1),
(Post no. 2, 2),
(Post no. 3, 3)
]
And the batching is not limited to inserting a single entity type. While cascading is not supported because the StatelessSession doesn’t have a Persistence Context, you can still insert parent and child entities and benefit from JDBC batch inserts, as illustrated by the following test case:
doInStatelessSession(session -> {
List<Post> posts = LongStream.rangeClosed(1, POST_COUNT).boxed()
.map(i -> {
Post post = new Post()
.setId(i)
.setTitle(
String.format(
"Post no. %d",
i
)
);
session.insert(post);
return post;
})
.toList();
final int COMMENT_COUNT = 5;
posts.forEach(post -> {
for (long i = 1; i <= COMMENT_COUNT; i++) {
session.insert(
new PostComment()
.setPost(post)
.setReview(
String.format(
"Post comment no. %d",
(post.getId() - 1) * COMMENT_COUNT + i
)
)
);
}
});
});
When executing the above integration test, Hibernate executes the following SQL INSERT statements:
Query:["
insert into post (title,id) values (?,?)
"],
Params:[
(Post no. 1, 1),
(Post no. 2, 2),
(Post no. 3, 3)
]
Query:["
insert into post_comment (post_id,review,id) values (?,?,?)
"],
Params:[
(1, Post comment no. 1, 1),
(1, Post comment no. 2, 2),
(1, Post comment no. 3, 3),
(1, Post comment no. 4, 4),
(1, Post comment no. 5, 5),
(2, Post comment no. 6, 6),
(2, Post comment no. 7, 7),
(2, Post comment no. 8, 8),
(2, Post comment no. 9, 9),
(2, Post comment no. 10, 10),
(3, Post comment no. 11, 11),
(3, Post comment no. 12, 12),
(3, Post comment no. 13, 13),
(3, Post comment no. 14, 14),
(3, Post comment no. 15, 15)
]
Batching UPDATE statements with the StatelessSession and Hibernate 6 and 5
Now, let’s assume we have loaded several Post entities and modified them while the entities are in the detached state:
List<Post> posts = doInJPA(entityManager -> {
return entityManager.createQuery("""
select p
from Post p
""", Post.class)
.setMaxResults(POST_COUNT)
.getResultList();
});
posts.forEach(post ->
post.setTitle(
post.getTitle().replaceAll("no", "nr")
)
);
To save the changes back to the database, we can use the update method of the Hibernate StatelessSession:
doInStatelessSession(session -> {
posts.forEach(session::update);
});
When executing the update method calls, we can see that Hibernate executes a single batch UPDATE statement:
Query:["
update post set title=? where id=?
"],
Params:[
(Post nr. 1, 1),
(Post nr. 2, 2),
(Post nr. 3, 3)
]
Batching DELETE statements with the StatelessSession and Hibernate 6 and 5
Let’s assume we have a list of Post entities that we need to delete. Because the Hibernate StatelessSession doesn’t support cascading, we will have to delete the child entities using a Bulk Delete Query prior to deleting the parent entities, as illustrated by the following example:
doInStatelessSession(session -> {
HibernateCriteriaBuilder builder = session.getCriteriaBuilder();
JpaCriteriaDelete<PostComment> criteria = builder
.createCriteriaDelete(PostComment.class);
Root<PostComment> post = criteria.from(PostComment.class);
session.createQuery(
criteria
.where(builder.in(post.get("post"), posts))
)
.executeUpdate();
posts.forEach(session::delete);
});
When running the above integration test, Hibernate is going to execute the following DELETE statements:
Query:["
delete from post_comment where post_id in (?,?,?)
"],
Params:[
(1, 2, 3)
]
Query:[
"delete from post where id=?
"], Params:[
(1),
(2),
(3)
]
The first statement is the Bulk Delete that removes the associated post_comment table records, and the second statement is the batched DELETE statement that removes the post entries.
That’s it!
If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.
Conclusion
As I explained in this article, if we need to update some detached entities, we can either use the JPA EntityManager merge method or the Hibernate Session update method.
The merge method will execute an unnecessary SELECT statement, and that’s an overhead to a batch processing task whose only goal is to update the entities it has changed according to a given business logic.
While the update method of the Hibernate Session forces the UPDATE statement without doing an extra SELECT, many developers will avoid this method since it’s been deprecated. In my opinion, the Session update method served a good purpose, and that’s why I opened the HHH-15915 issue to remove the deprecated flag.
In the meanwhile, if you have some entities in the detached state that need to be updated, you can use the StatelessSession update method as an alternative to the JPA merge method and avoid executing some extra SELECT statements.






