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 Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Express Hibernate Queries as Type-Safe Java Streams
  • Jakarta NoSQL 1.0: A Way To Bring Java and NoSQL Together
  • Java Stream API: 3 Things Every Developer Should Know About
  • Hibernate Validator vs Regex vs Manual Validation: Which One Is Faster?

Trending

  • Migrate a Hardcoded LangGraph Agent to LaunchDarkly AI Configs in 20 Minutes
  • Why DDoS Protection Is an Architectural Decision for Developers
  • OpenAPI From Code With Spring and Java: A Recipe for Your CI
  • From Indicators to Insights: Automating IOC Enrichment Using Python and Threat Feeds
  1. DZone
  2. Data Engineering
  3. Databases
  4. How JPAstreamer Can Help You Write Type-Safe Hibernate Code Without Unnecessary Complexity

How JPAstreamer Can Help You Write Type-Safe Hibernate Code Without Unnecessary Complexity

Learn how to use Hibernate in a type-safe manner without unnecessary complexity using standard Java Streams.

By 
Mislav Milicevic user avatar
Mislav Milicevic
·
Oct. 09, 20 · Tutorial
Likes (7)
Comment
Save
Tweet
Share
9.7K Views

Join the DZone community and get the full member experience.

Join For Free

For the past 10 or so years, JPA has been one of the most popular ways of accessing a database within the Java ecosystem. Even if you haven't heard of JPA directly, there is a high likelihood that you've heard of or even used one of the most popular implementations of the JPA API - Hibernate.

The reason why JPA is so popular is no secret - most of the inconveniences are abstracted from the user, making the API very intuitive to use. Let's say that in some random database exists a table called person with the following structure:

Plain Text
 




x


 
1
COLUMN NAME COLUMN TYPE
2
person_id   int
3
first_name  varchar(45)
4
last_name   varchar(45)
5
created_at  timestamp



To represent said table via JPA, users would typically create a class called Person which looks like this:

Java
 




xxxxxxxxxx
1
34


 
1
@Entity
2
@Table(name = "person")
3
public class Person {
4
 
           
5
    @Id
6
    @GeneratedValue(strategy = GenerationType.IDENTITY)
7
    @Column(name = "person_id", nullable = false, updatable = false)
8
    private Integer actorId;
9
 
           
10
    @Column(name = "first_name", nullable = false, columnDefinition = "varchar(45)")
11
    private String firstName;
12
 
           
13
    @Column(name = "last_name", nullable = false, columnDefinition = "varchar(45)")
14
    private String lastName;
15
 
           
16
    @Column(name = "created_at", nullable = false, updatable = false)
17
    private LocalDateTime createdAt;
18
 
           
19
    public Integer getActorId() {
20
        return actorId;
21
    }
22
 
           
23
    public String getFirstName() {
24
        return firstName;
25
    }
26
 
           
27
    public String getLastName() {
28
        return lastName;
29
    }
30
 
           
31
    public LocalDateTime getCreatedAt() {
32
        return createdAt;
33
    }
34
}



Once this initial setup is finished, users can use the EntityManager to create and execute queries. Let's say we wanted to retrieve information about a person with the id of 10. With JPA we could do something like this:

Java
 




xxxxxxxxxx
1


 
1
final EntityManager em = ...;
2
 
           
3
final Person person = em.createQuery("SELECT Person person FROM person WHERE person.id = 10", Person.class).getSingleResult();



I consider this to be a very efficient approach considering most of the heavy lifting is done for us. But not everything is sunshine and rainbows. Since we're creating queries using raw SQL, syntax errors are inevitable most of the time.

To combat this, a type-safe way of creating queries was introduced in JPA 2.0, named the Criteria API. Let's see how we can recreate the query above using the Criteria API:

Java
 




xxxxxxxxxx
1
12


 
1
final EntityManager entityManager = ...;
2
 
           
3
final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
4
final CriteriaQuery<Person> criteriaQuery = criteriaBuilder.createQuery(Person.class);
5
 
           
6
final Root<Person> root = criteriaQuery.from(Person.class);
7
criteriaQuery.select(root);
8
        
9
final Predicate condition = criteriaBuilder.equal(root.get("personId"), 1);
10
criteriaQuery.where(condition);
11
 
           
12
final Person person = entityManager.createQuery(criteriaQuery).getSingleResult();



The difference between the 2 approaches is quite evident. While we're able to create and execute queries in an almost type-safe way (e.g. root.get("personId") is not safe), our code has gotten substantially more complex. Ideally, we want to construct type-safe queries in a manner that doesn't introduce unnecessary complexity in our codebase.

In this article, I'll be showing you how to achieve this goal using JPAstreamer, a lightweight JPA extension that allows us to construct queries in a type-safe way using Java Streams.

Installing JPAstreamer

Before we can start using JPAstreamer we must install it as a dependency in our project. If you're using Maven as your build tool, add the following dependency:

XML
 




xxxxxxxxxx
1


 
1
<dependencies>
2
    ...
3
    <dependency>
4
        <groupId>com.speedment.jpastreamer</groupId>
5
        <artifactId>core</artifactId>
6
        <version>${jpastreamer-version}</version>
7
    </dependency>
8
    ...
9
</dependencies>



If you're using Gradle as your build tool, add the following dependency and annotation processor:

Groovy
 




xxxxxxxxxx
1


 
1
dependencies {
2
    compile "com.speedment.jpastreamer:core:${jpastreamer-version}"
3
    annotationProcessor "com.speedment.jpastreamer:fieldgenerator-standard:${jpastreamer-version}"
4
}



JPAstreamer Setup

To get access to a JPAstreamer instance, use the following bit of code:

Java
 




xxxxxxxxxx
1


 
1
final JPAStreamer jpaStreamer = JPAStreamer.of("my-persistence-unit"); 



Make sure you're using the correct persistence unit name when creating a JPAStreamer instance. I've called mine my-persistence-unit in the template above, so that's the name I will be passing to JPAStreamer::of.

Creating a Stream

The JPAStreamer instance has a stream() method which you can use to create a Stream for your entities. As an example, I'll use the Person entity created above as a Stream source:

Java
 




xxxxxxxxxx
1


 
1
final Stream<Person> personStream = jpaStreamer.stream(Person.class); 



Once you've obtained a Stream for our Entity, you can start writing queries using the Java Stream API.

Executing a Query

As a start, let's try to recreate the JPA query we created at the beginning of the article, but this time we'll use the Stream API:

Java
 




xxxxxxxxxx
1


 
1
final Optional<Person> result = personStream
2
    .filter(Person$.personId.equal(1))
3
    .findAny(); 



By terminating this Stream a series of optimizations and translations take place which in the end create and execute the following SQL Query:

MySQL
 




xxxxxxxxxx
1


 
1
select
2
    person0_.person_id as person_id1_0_,
3
    person0_.first_name as first_na2_0_,
4
    person0_.last_update as last_nam3_0_,
5
    person0_.created_at as created_4_0_,
6
from
7
    person person0_ 
8
where
9
    person0_.person_id=1



A More Complex Example

The example that we've been working with so far is rather simple, so let's try creating a JPA query that is a bit more complex in nature:

Java
 




xxxxxxxxxx
1
24


 
1
final EntityManager entityManager = ...;
2
 
           
3
final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
4
final CriteriaQuery<Person> criteriaQuery = criteriaBuilder.createQuery(Person.class);
5
 
           
6
final Root<Person> root = criteriaQuery.from(Person.class);
7
criteriaQuery.select(root);
8
        
9
final Predicate condition = criteriaBuilder.and(
10
    criteriaBuilder.like(root.get("firstName"), "C%"),
11
    criteriaBuilder.like(root.get("lastName"), "M%")
12
);
13
 
           
14
final Order primaryOrder = criteriaBuilder.asc(root.get("lastName"));
15
final Order secondaryOrder = criteriaBuilder.asc(root.get("firstName"));
16
 
           
17
criteriaQuery.where(condition);
18
criteriaQuery.orderBy(primaryOrder, secondaryOrder);
19
 
           
20
final TypedQuery<Person> query = entityManager.createQuery(criteriaQuery);
21
query.setFirstResult(5);
22
query.setMaxResult(10);
23
 
           
24
final List<Person> result = query.getResultList();



A lot of things are happening here, so let's break them down. The query will look for Person entities whose first name starts with the letter C and the last name starts with the letter M. Alongside these conditions, the results will be sorted by the last name and first name. Additionally, an offset of 5 is applied to the query, meaning the first 5 elements of the result will be skipped. Also, the result is limited to 10 entities.

Now let's see how this can be replicated using the Stream API:

Java
 




xxxxxxxxxx
1


 
1
jpaStreamer.stream(Person.class)
2
    .filter(Person$.firstName.startsWith("C").and(Person$.lastName.startsWith("lastName")))
3
    .sorted(Person$.lastName.comparator().thenComparing(Person$.firstName.comparator()))
4
    .skip(5)
5
    .limit(10)
6
    .collect(Collectors.toList())



The 2 approaches produce identical results, but the declarative style of programming that Java Streams provide us with makes our code less complex, allowing us to develop software a lot more efficiently.

Optional: Setting up a Persistence Unit

While this is not a JPA tutorial, it is important to mention that you must have a persistence unit ready. If you're adding JPAstreamer to an existing JPA project, then you probably have a persistence unit already created.

For those of you who are starting from scratch or have never configured JPA before, you must create a file named persistence.xml in your resources/META-INF folder. I'll provide you with a template for the persistence.xml file which you can modify:

XML
 




xxxxxxxxxx
1
18


 
1
<?xml version="1.0" encoding="UTF-8"?>
2
<persistence version="2.2"
3
             xmlns="http://xmlns.jcp.org/xml/ns/persistence"
4
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
6
     http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
7
 
           
8
    <persistence-unit name="my-persistence-unit" transaction-type="RESOURCE_LOCAL">
9
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
10
 
           
11
        <properties>
12
            <property name="javax.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver" />
13
            <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/db" />
14
            <property name="javax.persistence.jdbc.user" value="root" />
15
            <property name="javax.persistence.jdbc.password" value="password" />
16
        </properties>
17
    </persistence-unit>
18
</persistence>


 

Summary

JPA is a great API when it comes to database access, but its usage can easily lead to unnecessary complexity. With JPAstreamer, you're able to utilize JPA while keeping your codebase clean and maintainable.

GitHub: github.com/speedment/jpa-streamer
Homepage: jpastreamer.org
Documentation: speedment.github.io/jpa-streamer
Gitter Support Chat: gitter.im/jpa-streamer

Database Java (programming language) Stream (computing) Hibernate API

Published at DZone with permission of Mislav Milicevic. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Express Hibernate Queries as Type-Safe Java Streams
  • Jakarta NoSQL 1.0: A Way To Bring Java and NoSQL Together
  • Java Stream API: 3 Things Every Developer Should Know About
  • Hibernate Validator vs Regex vs Manual Validation: Which One Is Faster?

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

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

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook