Enterprise Java

How to get Type-Safe and Intuitive Hibernate/JPA Queries by Leveraging Java Streams

A large proportion of Java database applications are using Hibernate/JPA to bridge the gap between Java and SQL. Until recently, we were forced to mix Java and JPQL or to use complex imperative criteria builders to create database queries. Both of these methods are inherently neither type-safe nor very intuitive.

The newly launched open-source library JPAstreamer addresses these issues by allowing you to express Hibernate/JPA queries using Java Streams. This means we can avoid any impedance mismatches between JPQL/HQL and Java and get full type-safety. In this article, I will show you how to put Java Stream queries to work in your application using JPAstreamer.

JPAstreamer in a nutshell

As mentioned, JPAstreamer allows JPA queries to be expressed as standard Java Streams using short and concise, type-safe declarative constructs. This makes our code shorter, less complex, and easier to read and maintain. Best of all, we can stick to using only Java code without needing to mix it with SQL/JPQL or other language constructs/DSL. 

In short, we can query a database like this:

jpaStreamer.stream(Film.class)    .sorted(Film$.length.reversed())    .limit(15)    .map(Film$.title)    .forEach(System.out::println);

This prints the title of the 15 longest films in the database. 

OSS License 

JPAstreamer is using the same license as Hibernate (LGPL). This makes it easy to use in existing Hibernate projects. JPAstreamer also works with other JPA providers such as EclipseLink, OpenJPA, TopLink etc.

Installation

Installing JPAstreamer entails just adding a single dependency in your Maven/Gradle configuration file as described here. For example, Maven users add the following dependency:


    <dependency>
        <groupId>com.speedment.jpastreamer</groupId>
        <artifactId>jpastreamer-core</artifactId>
        <version>0.1.8</version>    </dependency>

Let’s have a look at how JPAstreamer fits in an existing application.

Example Database and JPA Entities

In the examples below, we are using JPAstreamer to query the “Sakila” example database that is available for download directly from Oracle or as a Docker instance.

This is how you install and run the example database using Docker:

 $ docker pull restsql/mysql-sakila$ docker run -d --publish 3306:3306 --name mysqld restsql/mysql-sakila

We will also be relying on JPA entities like the Film-class partly shown here:

@Entity@Table(name = "film", schema = "sakila")public class Film implements Serializable {    @Id    @GeneratedValue(strategy = GenerationType.IDENTITY)    @Column(name = "film_id", nullable = false, updatable = false, columnDefinition = "smallint(5)")    private Integer filmId;    @Column(name = "title", nullable = false, columnDefinition = "varchar(255)")    private String title;    @Column(name = "description", nullable = false, columnDefinition = "text")    private String description;    @ManyToMany(cascade = CascadeType.ALL)    @JoinTable(        name = "film_actor",        joinColumns = @JoinColumn(name = "film_id") ,        inverseJoinColumns = @JoinColumn(name = "actor_id") 
)    
private List<Artist> actors;    
...
}

The complete code in this article is open-sourced and available here

JPAstreamer – Printing the Longest Films

Here is a complete example of how we can use JPAstreamer to create a query that prints out the length and title of the 15 longest films in the database:

public class LongestFilms  {
    public static void main(String[] args) {
        final JPAStreamer jpaStreamer = JPAStreamer.of("sakila");
        jpaStreamer.stream(Film.class)            .sorted(Film$.length.reversed())            .limit(15)            .map(f -> String.format("%3d %s", f.getLength(), f.getTitle()))            .forEach(System.out::println);        
jpaStreamer.close();   
 }
}

This will print:

185 SOLDIERS EVOLUTION185 GANGS PRIDE185 SWEET BROTHERHOOD185 CHICAGO NORTH185 HOME PITY185 POND SEATTLE185 CONTROL ANTHEM185 DARN FORRESTER185 WORST BANGER184 SMOOCHY CONTROL184 SONS INTERVIEW184 SORORITY QUEEN184 MOONWALKER FOOL184 THEORY MERMAID

As can be seen, queries are simple, concise, completely type-safe and follow the Java Stream standard API. No need to learn new stuff.

The code above will create the following SQL (shortened for brevity):

select    film0_.film_id as film_id1_1_,    film0_.length as length4_1_,    film0_.title as title10_1_,    /* more columns */from    film film0_ order by    film0_.length desc limit ?

This means that most of the Java stream is actually executed on the database side. It is only the map() and forEach() operations (which cannot easily be translated to SQL) that are executed in the JVM. This is really cool!

Pre-Joining Columns

To avoid the “SELECT N + 1” problem, it is possible to configure streams to join in columns eagerly by providing a configuration object like this:

StreamConfiguration configuration = StreamConfiguration.of(Film.class)    .joining(Film$.actors)    .joining(Film$.language);jpaStreamer.stream(configuration)     .filter(Film$.rating.in("G", "PG"))    .forEach(System.out::println);

This will create a Hibernate join under the hood and will only render a single SQL query where all the Film fields “List<Artist> artists” and “Language language” will be populated on the fly:

select    Film from     Film as Film left join     fetch Film.actors as generatedAlias0 left join     fetch Film.language as GeneratedAlias1where     Film.rating in (        :param0, :param1    )

Conclusion

In this article, I have shown how you can avoid impedance mismatches between JPQL/HQL in Hibernate/JPA using the open-source library JPAstreamer. The Stream API allows you to compose type-safe and expressive database queries in standard Java without compromising the application performance. 

Feedback

The background to JPAStreamer is that we have developed the stream-based ORM-tool Speedment, and we have come across many developers that want to use Java streams but are constrained to use Hibernate in their applications. Therefore, we have now developed JPAstreamer, a JPA/Hibernate extension that handles Java Stream queries without the need to change the existing codebase. 

Take JPAStreamer for a spin and let me know what you like/dislike by dropping a message on Gitter!

Resources

Published on Java Code Geeks with permission by Per Minborg, partner at our JCG program. See the original article here: Extend Hibernate to Handle Java Stream Queries

Opinions expressed by Java Code Geeks contributors are their own.

Per Minborg

I am a guy living in Palo Alto, California, but I am originally from Sweden. I am working as CTO on Speedment with Java and database application acceleration. Check it out on www.speedment.com
Subscribe
Notify of
guest

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

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button