How to call SQL Server stored procedures and functions with JPA and Hibernate

Imagine having a tool that can automatically detect JPA and Hibernate performance issues. Wouldn’t that be just awesome?

Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, or Play Framework.

So, enjoy spending your time on the things you love rather than fixing performance issues in your production system on a Saturday night!

Introduction

This article is part of a series of posts related to calling various relational database systems stored procedures and database functions from Hibernate. The reason for writing this down is because there are many peculiarities related to the underlying JDBC driver support and not every JPA or Hibernate feature is supported on every relational database.

SQL Server stored procedures

SQL Server supports both stored procedures and functions, so first we’ll start with the following stored procedure that outputs a simple value.

SQL Server stored procedure outputting a simple value

CREATE PROCEDURE count_comments 
   @postId INT, 
   @commentCount INT OUTPUT 
AS 
BEGIN 
   SELECT @commentCount = COUNT(*)  
   FROM post_comment  
   WHERE post_id = @postId 
END;

This stored procedure has two parameters: an input parameter (e.g. postId) and an output parameter (e.g. commentCount) which is used to return the count of post_comment records associated with a given post_id parent row.

To call this stored procedure, you can use the following Java Persistence API 2.1 syntax:

StoredProcedureQuery query = entityManager
.createStoredProcedureQuery("count_comments")
.registerStoredProcedureParameter(
    "postId", 
    Long.class, 
    ParameterMode.IN
)
.registerStoredProcedureParameter(
    "commentCount", 
    Long.class, 
    ParameterMode.OUT
)
.setParameter("postId", 1L);

query.execute();

Long commentCount = (Long) query.getOutputParameterValue("commentCount");

By default, the underlying JDBC CallableStatement remains open even after executing the stored procedure and fetching the OUT or REF_CURSOR parameters.

To explicitly close the CallableStatement, you have to call release on the underlying ProcedureOutputs object as illustrated in the following example:

  StoredProcedureQuery query = entityManager
  .createStoredProcedureQuery("count_comments")
  .registerStoredProcedureParameter(
      "postId", 
      Long.class, 
      ParameterMode.IN
  )
  .registerStoredProcedureParameter(
      "commentCount", 
      Long.class, 
      ParameterMode.OUT
  )
  .setParameter("postId", 1L);
  
  try {
      query.execute();
      
      Long commentCount = (Long) query
        .getOutputParameterValue("commentCount");
  
      assertEquals(Long.valueOf(2), commentCount);
  } finally {
      query.unwrap(ProcedureOutputs.class)
      .release();
  }
  

For more details, check out this article.

SQL Server stored procedure outputting a SYS_REFCURSOR

A stored procedure can also define an SYS_REFCURSOR output parameter which is associated with a database cursor that can be iterated to fetch multiple database records:

CREATE PROCEDURE post_comments 
    @postId INT, 
    @postComments CURSOR VARYING OUTPUT 
AS 
    SET NOCOUNT ON; 
    SET @postComments = CURSOR 
    FORWARD_ONLY STATIC FOR 
        SELECT *  
        FROM post_comment   
        WHERE post_id = @postId;  
    OPEN @postComments;

When trying to call this stored procedure:

StoredProcedureQuery query = entityManager
.createStoredProcedureQuery("post_comments")
.registerStoredProcedureParameter(
    1, 
    Long.class, 
    ParameterMode.IN
)
.registerStoredProcedureParameter(
    2, 
    Class.class, 
    ParameterMode.REF_CURSOR
)
.setParameter(1, 1L);

query.execute();
List<Object[]> postComments = query.getResultList();

Hibernate throws the following exception:

org.hibernate.QueryException: Dialect [org.hibernate.dialect.SQLServer2012Dialect] not known to support REF_CURSOR parameters

Even though this stored procedure is working properly on Oracle and PostgreSQL, on SQL Server, it does not work because the JDBC 4.2 Microsoft SQL Server driver does not support this feature, as clearly stated in the driver documentation.

SQL Server functions

SQL Server also supports database functions, which, unlike stored procedures, don’t use input and output parameters, but one or more function arguments and a single return value.

SQL Server function returning a simple value

The first stored procedure can be turned into a function which looks like this:

CREATE FUNCTION fn_count_comments (@postId INT)
RETURNS INT  
AS  
BEGIN  
    DECLARE @commentCount int;  
    SELECT @commentCount = COUNT(*) 
    FROM post_comment   
    WHERE post_id = @postId;  
    RETURN(@commentCount);  
END;

Unfortunately, as of writing (Hibernate 5.1.0), both the Java Persistence 2.1 stored procedure and the Hibernate-specific API cannot be used to call functions.
However, there are several workarounds for this limitation.

Fortunately, we can call the database function using plain JDBC API:

int commentCount = session.doReturningWork(connection -> {
    try (CallableStatement function = connection
        .prepareCall(
            "{ ? = call fn_count_comments(?) }")) {
        function.registerOutParameter(1, Types.INTEGER);
        function.setInt(2, 1);
        function.execute();
        return function.getInt(1);
    }
});

I'm running an online workshop on the 20-21 and 23-24 of November about High-Performance Java Persistence.

If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.

Conclusion

Calling stored procedures and functions is not difficult at all, but it requires knowing some details about Hibernate and the underlying JDBC driver capabilities. The next article is about calling PostgreSQL stored procedures and functions, so stay tuned!

Transactions and Concurrency Control eBook

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.