Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Native SQL

JPQL covers most needs, but sometimes you need SQL itself: a function specific to your database, a window function, a hand-tuned report query. Native queries let you send any SQL through the same EntityManager, with parameters and within the current transaction. The price is portability: the query is written for one particular database.

Loading entities

The examples use the Seed data from the lesson on lazy loading.

// src/main/java/com/example/blog/Main.java
List<Post> posts = em.createNativeQuery(
                "select * from Post where views > :min order by views desc", Post.class)
        .setParameter("min", 50)
        .getResultList();
posts.forEach(p -> IO.println(p.getTitle()));
Hibernate: select * from Post where views > ? order by views desc
Hello, Hibernate
Records in Java 25

The query uses real table and column names: Post, views, author_id. With the entity class as the second argument, Hibernate turns each row into a managed Post. Named parameters like :min work here too. The method returns a raw List, so the compiler warns about an unchecked conversion.

Rows as arrays

When the result is not an entity, ask for Object[] — one array per row:

// src/main/java/com/example/blog/Main.java
List<Object[]> rows = em.createNativeQuery("""
                select a.name, count(p.id)
                from Author a left join Post p on p.author_id = a.id
                group by a.name
                order by a.name
                """, Object[].class)
        .getResultList();
rows.forEach(r -> IO.println(Arrays.toString(r)));
[Anna, 1]
[Maria, 1]
[Oleg, 1]

The element types depend on the JDBC driver: here the name is a String and the count a Long.

Rows into a record

Hibernate can also call the constructor of a record whose components match the columns:

// src/main/java/com/example/blog/AuthorPosts.java
package com.example.blog;
 
public record AuthorPosts(String name, long posts) {
}
// src/main/java/com/example/blog/Main.java
List<AuthorPosts> stats = em.createNativeQuery("""
                select a.name, count(p.id)
                from Author a left join Post p on p.author_id = a.id
                group by a.name
                order by a.name
                """, AuthorPosts.class)
        .getResultList();
stats.forEach(IO::println);
AuthorPosts[name=Anna, posts=1]
AuthorPosts[name=Maria, posts=1]
AuthorPosts[name=Oleg, posts=1]

The columns are matched by position: the first to name, the second to posts.

When to choose what

TaskTool
Ordinary selection and filteringJPQL
Filters assembled at run timeCriteria API
Database-specific features, complex reportsNative SQL

Native queries also bypass the persistence context, like bulk JPQL updates: an update through SQL does not change entities already loaded.

Common mistake

When a native query loads entities, it must return every column the entity maps. If you select only a few:

// src/main/java/com/example/blog/Main.java
em.createNativeQuery("select id, title from Post where views > :min order by views desc", Post.class)
Exception in thread "main" org.hibernate.exception.SQLGrammarException: Unable to find column position by name: author_id [Column "AUTHOR_ID" not found [42122-252]] [n/a]

To load a part of the columns, use Object[] or a record instead of the entity class.

Thanks for reading!

Donate to the site — we work hard for you.