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
- GitHub: github.com/speedment/jpa-streamer
- Homepage: jpastreamer.org
- Documentation: github.io/jpa-streamer
- Gitter Support Chat for questions and feedback: gitter.im/jpa-streamer
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. |