How to get Type-Safe and Intuitive Hibernate/JPA Queries
Learn how to use standard Java streams which automatically convert themselves to Hibernate/JPA Queries.
Join the DZone community and get the full member experience.
Join For FreeJPAstreamer 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:
x
<dependency>
<groupId>com.speedment.jpastreamer</groupId>
<artifactId>jpastreamer-core</artifactId>
<!-- Make sure to use the latest version -->
<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:
xxxxxxxxxx
$ 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:
xxxxxxxxxx
name = "film", schema = "sakila") (
public class Film implements Serializable {
strategy = GenerationType.IDENTITY) (
name = "film_id", nullable = false, updatable = false, columnDefinition = "smallint(5)") (
private Integer filmId;
name = "title", nullable = false, columnDefinition = "varchar(255)") (
private String title;
name = "description", nullable = false, columnDefinition = "text") (
private String description;
cascade = CascadeType.ALL) (
(
name = "film_actor",
joinColumns = (name = "film_id") ,
inverseJoinColumns = (name = "actor_id")
)
private List<Actor>;
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:
xxxxxxxxxx
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:
xxxxxxxxxx
185 SOLDIERS EVOLUTION
185 GANGS PRIDE
185 SWEET BROTHERHOOD
185 CHICAGO NORTH
185 HOME PITY
185 POND SEATTLE
185 CONTROL ANTHEM
185 DARN FORRESTER
185 WORST BANGER
184 SMOOCHY CONTROL
184 SONS INTERVIEW
184 SORORITY QUEEN
184 MOONWALKER FOOL
184 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):
xxxxxxxxxx
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:
xxxxxxxxxx
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:
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.
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: https://speedment.github.io/jpa-streamer/jpa-streamer/0.1.8/introduction/introduction.html
- Gitter Support Chat for questions and feedback: gitter.im/jpa-streamer
Published at DZone with permission of Per-Åke Minborg, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments