How does Hibernate handle JPA Criteria API literals

Introduction

The JPA specification is like a Java interface, However, when it comes to performance, implementation details matter a lot. That’s why, even if you use the JPA standard, you still need to know how the underlying provider implements the standard specification.

For instance, if we take this tweet from Gareth Western:

We can clearly see that there is an issue in the way literals might be handled by Hibernate when executing a Criteria API query.

Therefore, in this article, we are going to see how literals are handled by Hibernate and explain what we have changed in Hibernate 5.2.12.

Domain Model

Let’s assume we have the following JPA entity:

@Entity(name = "Book")
@Table(name = "book")
public class Book {

    @Id
    private Long id;

    private String name;

    @NaturalId
    private long isbn;

    //Getters and setters omitted for brevity
}

For more details about the @NaturalId annotation, check out this article.

And, we have the following Book entry in our database:

Book book = new Book();
book.setId(1L);
book.setName("High-Performance Java Persistence");
book.setIsbn(978_9730228236L);

entityManager.persist(book);

Default literal handling mode

When executing the following Criteria API query:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();

CriteriaQuery<Book> cq = cb.createQuery(Book.class);
Root<Book> root = cq.from(Book.class);
cq.select(root);
cq.where(
    cb.equal(
        root.get("isbn"), 
        978_9730228236L
    )
);

Book book = entityManager.createQuery(cq).getSingleResult();

assertEquals(
    "High-Performance Java Persistence", 
    book.getName()
);

Hibernate generates the following SQL query:

SELECT 
    b.id AS id1_0_,
    b.isbn AS isbn2_0_,
    b.name AS name3_0_
FROM 
    book b
WHERE 
    b.isbn = 9789730228236

As expected, the literal value was inlined in the generated SQL query.

However, when using a String literal:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();

CriteriaQuery<Book> cq = cb.createQuery(Book.class);
Root<Book> root = cq.from(Book.class);
cq.select(root);
cq.where(
    cb.equal(
        root.get("name"), 
        "High-Performance Java Persistence"
    )
);

Book book = entityManager.createQuery(cq).getSingleResult();

assertEquals(
    978_9730228236L, 
    book.getIsbn()
);

Hibernate generates the following SQL query:

SELECT 
    b.id AS id1_0_,
    b.isbn AS isbn2_0_,
    b.name AS name3_0_
FROM 
    book b
WHERE 
    b.name = ?

The literal is gone! Instead, we now got a PreparedStatement bind parameter.

Now, depending on the use case, you either want to use inlined literal values or substitute them with bind parameters. The advantage of using bind parameters is that the query Parse Tree and the Execution Plan (e.g. Oracle, SQL Server) could be cached.

However, sometimes caching the Execution Plan can cause more harm than good, especially if the literal values are skewed or there is a lot of contention on the Execution Plan Cache.

For this purpose, the HHH-9576 Jira issue was created.

Confguring the literal handling mode

Since Hibernate 5.2.12, you can use the LiteralHandlingMode to define the strategy used for handling literals in Criteria API queries. This enumeration takes three values:

  • AUTO, which works exactly as you’ve just seen. Numeric values are inlined while String-based ones are substituted with bind parameters.
  • INLINE, which will inline both numeric and String-based values.
  • BIND, which will substitute both numeric and String-based literals with bind parameters.

So, if we provide the INLINE handling mode:

properties.put(
    AvailableSettings.CRITERIA_LITERAL_HANDLING_MODE, 
    LiteralHandlingMode.INLINE
);

And, rerun the String-based example, Hibernate will now execute the following SQL query:

SELECT 
    b.id AS id1_0_,
    b.isbn AS isbn2_0_,
    b.name AS name3_0_
FROM 
    book b
WHERE 
    name = 'High-Performance Java Persistence'

Or, if we change it to BIND:

properties.put(
    AvailableSettings.CRITERIA_LITERAL_HANDLING_MODE, 
    LiteralHandlingMode.BIND
);

And rerun the numeric literal example, you will now get the following SQL query:

SELECT 
    b.id AS id1_0_,
    b.isbn AS isbn2_0_,
    b.name AS name3_0_
FROM 
    book b
WHERE 
    b.isbn = ?

That’s it!

If you enjoyed this article, I bet you are going to love my book as well.

Conclusion

Specifying the expected Criteria API literal handling mode is actually a very nice enhancement. While the default AUTO mode might work just fine for many data access layers, in case you need a different behavior, just provide the LiteralHandlingMode strategy you want to use, and Hibernate will switch to using that one instead.

Enter your email address to follow this blog and receive notifications of new posts by email.

Advertisements

15 thoughts on “How does Hibernate handle JPA Criteria API literals

  1. Hello Vlad,
    thanks for the article and all your work on hibernate. I have a question.
    How do I set the property ‘hibernate.criteria.literal_handling_mode’ via the persistence.xml?
    Is it possible to set an enum value in persistence.xml?
    Thank you.
    Oliver

  2. Hi Vlad,

    It seems like this Literal Handling Mode configuration only works for the whole application, right? So, can’t I configure it only for a specific query or use case?

    1. Numeric values can never lead to SQL injection because they are always bound either as a primitive or a Number wrapper. For String literals, there would be such risk, hence the usage of bind parameters.

      However, when using INLINE, we do escape the incoming literal, so SQL injection should be prevented.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s