DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
The Latest "Software Integration: The Intersection of APIs, Microservices, and Cloud-Based Systems" Trend Report
Get the report
  1. DZone
  2. Coding
  3. Java
  4. Express Hibernate Queries as Type-Safe Java Streams

Express Hibernate Queries as Type-Safe Java Streams

In this article, you will learn how the JPAstreamer Quarkus extension facilitates type-safe Hibernate queries without unnecessary wordiness and complexity.

Julia Gustafsson user avatar by
Julia Gustafsson
·
Jan. 30, 23 · Tutorial
Like (11)
Save
Tweet
Share
7.53K Views

Join the DZone community and get the full member experience.

Join For Free

As much as the JPA Criteria builder is expressive, JPA queries are often equally verbose, and the API itself can be unintuitive to use, especially for newcomers. In the Quarkus ecosystem, Panache is a partial remedy for these problems when using Hibernate. Still, I find myself juggling the Panache’s helper methods, preconfigured enums, and raw strings when composing anything but the simplest of queries. You could claim I am just inexperienced and impatient or, instead, acknowledge that the perfect API is frictionless to use for everyone. Thus, the user experience of writing JPA queries can be further improved in that direction. 

Introduction

One of the remaining shortcomings is that raw strings are inherently not type-safe, meaning my IDE rejects me the helping hand of code completion and wishes me good luck at best. On the upside, Quarkus facilitates application relaunches in a split second to issue quick verdicts on my code. And nothing beats the heart-felt joy and genuine surprise when I have composed a working query on the fifth, rather than the tenth, attempt... 

With this in mind, we built the open-source library JPAstreamer to make the process of writing Hibernate queries more intuitive and less time-consuming while leaving your existing codebase intact. It achieves this goal by allowing queries to be expressed as standard Java Streams. Upon execution, JPAstreamer translates the stream pipeline to an HQL query for efficient execution and avoids materializing anything but the relevant results.  

Let me take an example—in some random database exists a table called Person represented in a Hibernate application by the following standard Entity:

Java
 
@Entity
@Table(name = "person")
public class Person {
  
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "person_id", nullable = false, updatable = false)
    private Integer actorId;

    @Column(name = "first_name", nullable = false, columnDefinition = "varchar(45)")
    private String firstName;

    @Column(name = "last_name", nullable = false, columnDefinition = "varchar(45)")
    private String lastName;

    @Column(name = "created_at", nullable = false, updatable = false)
    private LocalDateTime createdAt;

	// Getters for all fields will follow from here 
}


To fetch the Person with an Id of 1 using JPAstreamer, all you need is the following:

Java
 
@ApplicationScoped
public class PersonRepository {
	
    @PersistenceContext
    EntityManagerFactory entityManagerFactory;

    private final JPAStreamer jpaStreamer;

    public PersonRepository EntityManagerFactory entityManagerFactory) {
		jpaStreamer = JPAStreamer.of(entityManagerFactory); <1>
    }

    @Override
    public Optional<Person> getPersonById(int id) {
        return this.jpaStreamer.from(Person.class) <2>
            .filter(Person$.personId.equal(id)) <3>
            .findAny();
    }
  
}


<1> Initialize JPAstreamer in one line, the underlying JPA provider handles the DB configuration.

<2> The stream source is set to be the Person table.

<3> The filter operation is treated as an SQL WHERE clause and the condition is expressed type-safely with JPAstreamer predicates (more on this to follow). 

Despite it looking as if JPAstreamer operates on all Person objects, the pipeline is optimized to a single query, in this case:

Plain Text
 
select
    person0_.person_id as person_id1_0_,
    person0_.first_name as first_na2_0_,
    person0_.last_name as last_nam3_0_,
    person0_.created_at as created_4_0_,
from
    person person0_
where
    person0_.person_id=1


Thus, only the Person matching the search criteria is ever materialized. 

Next, we can look at a more complex example in which I am searching for Person’s with a first name ending with an “A” and a last name that starts with “B.” The matches are sorted primarily by first name and secondly by last name. I further decide to apply an offset of 5, excluding the first five results, and to limit the total results to 10. Here is the stream pipeline to achieve this task:

Java
 
List<Person> list = jpaStreamer.stream(Person.class)
	.filter(Person$.firstName.endsWith("A").and(Person$.lastName.startsWith("B"))) <1>
	.sorted(Person$.firstName.comparator().thenComparing(Person$.lastName.comparator())) <2>
	.skip(5) <3> 
	.limit(10) <4>
	.collect(Collectors.toList())


<1> Filters can be combined with the and/or operators.

<2> Easily filter on one or more properties.

<3> Skip the first 5 Persons.

<4> Return at most 10 Persons.

In the context of queries, the stream operators filter, sort, limit, and skip, all have a natural mapping that makes the resulting query expressive and intuitive to read while remaining compact. 

This query is translated by JPAstreamer to the following HQL statement:

Plain Text
 
select
    person0_.person_id as person_id1_0_,
    person0_.first_name as first_na2_0_,
    person0_.last_name as last_nam3_0_,
    person0_.created_at as created_4_0_,
from
    person person0_
where
    person0_.person_id=1
where
    (person0_.first_name like ?) 
    and (person0_.last_name like ?) 
order by
    person0_.first_name asc,
    person0_.last_name asc limit ?, ?


How JPAstreamer Works

Okay, it looks simple. But how does it work? JPAstreamer uses an annotation processor to form a meta-model at compile time. It inspects any classes marked with the standard JPA annotation @Entity, and for every entity Foo.class, a corresponding Foo$.class is created. The generated classes represent entity attributes as Fields used to form predicates on the form User$.firstName.startsWith("A") that can be interpreted by JPAstreamer’s query optimizer.

It is worth repeating that JPAstreamer does not alter or disturb the existing codebase but merely extends the API to handle Java stream queries.

Installing the JPAstreamer Extension

JPAstreamer is installed as any other Quarkus extension, using a Maven dependency:

XML
 
<dependency>
	<groupId>io.quarkiverse.jpastreamer</groupId>
	<artifactId>quarkus-jpastreamer</artifactId>
	<version>1.0.0</version>
</dependency>


After the dependency is added, rebuild your Quarkus application to trigger JPAstreamer’s annotation processor. The installation is complete once the generated fields reside in /target/generated-sources; you’ll recognize them by the trailing $ in the class names, e.g., Person$.class.

Note: JPAstreamer requires an underlying JPA provider, such as Hibernate. For this reason, JPAstreamer needs no additional configuration as the database integration is taken care of by the JPA provider.

JPAstreamer and Panache 

Any Panache fan will note that JPAstreamer shares some of its objectives with Panache, in simplifying many common queries. Still, JPAstreamer distinguishes itself by instilling more confidence in the queries with its type-safe stream interface. However, no one is forced to take a pick as Panache and JPAstreamer work seamlessly alongside each other.

Note: Here is an example Quarkus application that uses both JPAstreamer and Panache.

At the time of writing, JPAstreamer does not have support for Panache’s Active Record Pattern, as it relies on standard JPA Entities to generate its meta-model. This will likely change in the near future. 

Summary 

JPA in general, and Hibernate have greatly simplified application database access, but its API sometimes forces unnecessary complexity. With JPAstreamer, you can utilize JPA while keeping your codebase clean and maintainable.

API Plain text Quarkus Hibernate Java (programming language) sql Stream (computing) Apache Maven Open source Strings

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • 10 Things to Know When Using SHACL With GraphDB
  • 4 Best dApp Frameworks for First-Time Ethereum Developers
  • Best Practices for Setting up Monitoring Operations for Your AI Team
  • Building a REST API With AWS Gateway and Python

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: