Hibernate StatelessSession Upsert

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

In this article, we are going to see how the Hibernate StatelessSession Upsert method works.

As I explained in this article, the UPSERT operation allows you to INSERT a record if there is no such record matching the filtering criteria or to UPDATE it in case the record exists.

While many relational database systems offer the standard MERGE command to execute the UPSERT command, the syntax may differ slightly from one database to the other. Therefore, it’s great that Hibernate provides a portable way to achieve this goal.

Domain Model

Let’s assume we are using the following Book entity:

@Entity
@Table(name = "book")
public class Book {
⠀
    @Id
    private Long id;
⠀
    private String title;
⠀
    @NaturalId
    private String isbn;
⠀
    public Long getId() {
        return id;
    }
⠀
    public Book setId(Long id) {
        this.id = id;
        return this;
    }
⠀
    public String getTitle() {
        return title;
    }
⠀
    public Book setTitle(String title) {
        this.title = title;
        return this;
    }
⠀
    public String getIsbn() {
        return isbn;
    }
⠀
    public Book setIsbn(String isbn) {
        this.isbn = isbn;
        return this;
    }
}

Hibernate StatelessSession Upsert

Since version 6.3, the Hibernate StatelessSession utility provides an Upsert method we can use to either INSERT or UPDATE a JPA entity.

Executing the Upsert on PostgreSQL

When executing the following test case on PostreSQL:

doInStatelessHibernate(session -> {
    session.upsert(
        new Book()
            .setId(1L)
            .setTitle("High-Performance Hibernate")
            .setIsbn("978-9730228236")
    );
⠀
    session.upsert(
        new Book()
            .setId(1L)
            .setTitle("High-Performance Hibernate 2nd edition")
            .setIsbn("978-9730228236")
    );
});

Hibernate generates the following SQL statements:

Query:["
    MERGE INTO
        book AS t
    USING (
        SELECT 
            CAST(? as bigint) id,
            CAST(? as text) title
    ) AS s
    ON (t.id = s.id)
    WHEN NOT MATCHED THEN
        INSERT (id, title) VALUES (s.id, s.title)
    WHEN MATCHED THEN
        UPDATE SET title = s.title
"], 
Params:[(1, High-Performance Hibernate)]

Query:["
    MERGE INTO
        book AS t
    USING (
        SELECT 
            CAST(? as bigint) id,
            CAST(? as text) title
    ) AS s
    ON (t.id = s.id)
    WHEN NOT MATCHED THEN
        INSERT (id, title) VALUES (s.id, s.title)
    WHEN MATCHED THEN
        UPDATE SET title = s.title
"], 
Params:[(1, High-Performance Hibernate 2nd edition)]

Executing the Upsert on Oracle

When executing the same test case on Oracle, Hibernate generates the following SQL statements:

Query:["
    MERGE INTO
        book t
    USING (
        SELECT
            CAST(? as number(19,0)) id,
            CAST(? as varchar2(4000 char)) title
        FROM
            dual
    ) s ON (t.id=s.id)
    WHEN NOT MATCHED THEN
        INSERT (id, title) VALUES (s.id, s.title) 
    WHEN MATCHED THEN
        UPDATE SET t.title = s.title
"],
Params:[(1, High-Performance Hibernate)]

Query:["
    MERGE INTO
        book t
    USING (
        SELECT
            CAST(? as number(19,0)) id,
            CAST(? as varchar2(4000 char)) title
        FROM
            dual
    ) s ON (t.id=s.id)
    WHEN NOT MATCHED THEN
        INSERT (id, title) VALUES (s.id, s.title) 
    WHEN MATCHED THEN
        UPDATE SET t.title = s.title
"],
Params:[(1, High-Performance Hibernate 2nd edition)]

Notice the FROM dual part that’s specific to Oracle.

Executing the Upsert on SQL Server

When executing the test case on SQL Server, Hibernate generates the MERGE SQL statement as well:

Query:["
    MERGE INTO
        book AS t USING (
            SELECT
                CAST(? as bigint) id,
                CAST(? as varchar(max)) title
        ) AS s ON (t.id=s.id)
    WHEN NOT MATCHED THEN
    INSERT (id, title) VALUES (s.id, s.title)
    WHEN MATCHED THEN
    UPDATE SET title = s.title;
"], 
Params:[(1, High-Performance Hibernate)]

Query:["
    MERGE INTO
        book AS t USING (
            SELECT
                CAST(? as bigint) id,
                CAST(? as varchar(max)) title
        ) AS s ON (t.id=s.id)
    WHEN NOT MATCHED THEN
    INSERT (id, title) VALUES (s.id, s.title)
    WHEN MATCHED THEN
    UPDATE SET title = s.title;"], 
Params:[(1, High-Performance Hibernate 2nd edition)]

Since SQL Server supports the MERGE statement, Hibernate uses just like on PostgreSQL and Oracle.

Executing the Upsert on MySQL

When executing the same test case on MySQL, Hibernate generates a pair of UPDATE and INSERT SQL statements:

Query:["
    UPDATE
        book
    SET 
        title = ?
    WHERE
        id = ?
"],
Params:[(High-Performance Hibernate, 1)]

Query:["
    INSERT INTO
        book (title,id)
    VALUES
        (?,?)
"],
Params:[(High-Performance Hibernate, 1)]

Query:["
    UPDATE
        book
    SET 
        title = ?
    WHERE
        id = ?
"],
Params:[(High-Performance Hibernate 2nd edition, 1)]

In the case of MySQL, since the MERGE is not supported, the upsert method will simply render an UPDATE followed by an INSERT instead of using the ON DUPLICATE KEY clause.

The reason why the second UPDATE is not followed by an INSERT is because the UPDATE call returned the updateCount value of 1, so Hibernate knows that the UPDATE went through.

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

The Hibernate StatelessSession Upsert method can use the MERGE SQL command if the database supports it, like it’s the case with PostgreSQL, Oracle, or SQL Server.

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.