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

  • Metadata, Not Data Volume, Is the Real Bottleneck in Modern Data Lakes
  • Building Unified, Access-Aware Search in a Data Mesh
  • From Data Lakes to Intelligence Lakes: Augmenting Apache Iceberg With Generative AI Metadata on AWS
  • Stabilizing ETL Pipelines With Airflow, Presto, and Metadata Contracts

Trending

  • The ORM Is Over: AI-Written SQL Is the New Data Access Layer
  • Key Takeaways From Integrating a RAG Application With LangSmith
  • Introduction to Tactical DDD With Java: Steps to Build Semantic Code
  • Lambda-Driven API Design: Building Composable Node.js Endpoints With Functional Primitives
  1. DZone
  2. Data Engineering
  3. Data
  4. Querydsl vs. JPA Criteria, Part 2: Metamodel

Querydsl vs. JPA Criteria, Part 2: Metamodel

In part two of a series dedicated to the Querydsl framework, this tutorial demonstrates how to use a metamodel with JPA Criteria and Querydsl.

By 
Arnošt Havelka user avatar
Arnošt Havelka
DZone Core CORE ·
Oct. 17, 22 · Tutorial
Likes (4)
Comment
Save
Tweet
Share
8.5K Views

Join the DZone community and get the full member experience.

Join For Free

This is the second article in my series dedicated to the Querydsl framework. I planned to shed light on the custom queries, as promised in the first article, but I decided to explain the metamodel usage first in order to simplify the explanation later on.

So far, this series contains these articles:

  • Introduction
  • Metadata (this article) 

In This Article, You Will Learn:

  • What is the JPA Metamodel? 
  • Using metadata with JPA Criteria
  • Using metadata with Querydsl

What Is the JPA Metamodel?

The Canonical Metamodel used by JPA was introduced in order to tackle the following issues:

  • Provide an easy way to offer all available attributes: We can use auto-completion provided by our IDE to find the desired attribute.
  • Make refactoring easier: We can easily identify all the affected queries when we change any attribute's name.

Basically, we want to avoid literal values in our queries and rely on a generated type-safe metamodel class. The goal of the JPA Static Metamodel Generator is to automatically generate at the build time the Metamodel classes from our entities. Therefore, we can keep our queries up-to-date.

Note: Read another good explanation of a need for the JPA Metamodel. 

JPA Criteria

Let's start with JPA Criteria as it represents a traditional approach.

Recapitulation

Let's use a findAllCitiesBy method to search cities by some attributes like this:

Java
 
public List<City> findAllCitiesBy(@NonNull String cityName, @NonNull String cityState, @NonNull String countryName) {
	var cb = em.getCriteriaBuilder();
	var query = cb.createQuery(City.class);
	Root<City> cityRoot = query.from(City.class);
	List<Predicate> predicates = new ArrayList<>();

	predicates.add(cb.like(cityRoot.get("name"), cityName));
	predicates.add(cb.like(cityRoot.get("state"), cityState));
	predicates.add(cb.equal(cityRoot.get("country").get("name"), cb.literal(countryName)));

	query.where(predicates.toArray(new Predicate[0]));
	return em.createQuery(query).getResultList();
}


Note: This method was introduced in the "Custom Queries" chapter in the introductory article of this series, as linked above.

We can see several literal values on lines 7-9 (e.g., name or state). Our goal is to get rid of them and use the generated metamodel instead.

Maven Configuration

First of all, we need to add the hibernate-jpamodelgen dependency to our Maven project (pom.xml). We can find the latest available version in the Maven Central repository.

XML
 
<dependency>
	<groupId>org.hibernate</groupId>
	<artifactId>hibernate-jpamodelgen</artifactId>
	<version>5.6.11.Final</version>
</dependency>


Note: The version is not the latest one (as you can see by the provided link) because we use the version governed by Spring Boot. Honestly, we can skip it entirely. The version is mentioned here just for clarity.

Generated Metamodel

The hibernate-jpamodelgen dependency generates classes with the "_" suffix. In our case, we have City_ class defined as:

Java
 
@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
@StaticMetamodel(City.class)
public abstract class City_ {

	public static volatile SingularAttribute<City, Country> country;
	public static volatile SingularAttribute<City, String> name;
	public static volatile SingularAttribute<City, Long> id;
	public static volatile SingularAttribute<City, String> state;

	public static final String COUNTRY = "country";
	public static final String NAME = "name";
	public static final String ID = "id";
	public static final String STATE = "state";

}


Now, we can either switch from literal to constant (e.g., COUNTRY) or use type-safe references (e.g., country).

Usage

Let's see some very similar options for using the generated metamodel.

Constant for a Literal

The easiest option is just to replace the literal value with the generated one (see lines 7-9).

Java
 
public List<City> findAllCitiesBy(@NonNull String cityName, @NonNull String cityState, @NonNull String countryName) {
	var cb = em.getCriteriaBuilder();
	var query = cb.createQuery(City.class);
	Root<City> cityRoot = query.from(City.class);
	List<Predicate> predicates = new ArrayList<>();

	predicates.add(cb.like(cityRoot.get(City_.NAME), cityName));
	predicates.add(cb.like(cityRoot.get(City_.STATE), cityState));
	predicates.add(cb.equal(cityRoot.get(City_.COUNTRY).get(Country_.NAME), cb.literal(countryName)));

	query.where(predicates.toArray(new Predicate[0]));
	return em.createQuery(query).getResultList();
}


Constant With a Type

We can also use a type-safe way with constants using, for example, the SingularAttribute type.

Java
 
public List<City> findAllCitiesBy(@NonNull String cityName, @NonNull String cityState, @NonNull String countryName) {
	...
	predicates.add(cb.like(cityRoot.get(City_.name), cityName));
	predicates.add(cb.like(cityRoot.get(City_.state), cityState));
	predicates.add(cb.equal(cityRoot.get(City_.country).get(Country_.name), cb.literal(countryName)));
	...
}


Note: There are more types for other cases, but it's out of the scope of this article.

Static Import

We can even simplify it with static imports.

Java
 
import static com.github.aha.sat.jpa.city.City_.country;
import static com.github.aha.sat.jpa.city.City_.name;
import static com.github.aha.sat.jpa.city.City_.state;

public List<City> findAllCitiesBy(@NonNull String cityName, @NonNull String cityState, @NonNull String countryName) {
	...
	predicates.add(cb.like(cityRoot.get(name), cityName));
	predicates.add(cb.like(cityRoot.get(state), cityState));
	predicates.add(cb.equal(cityRoot.get(country).get(Country_.name), cb.literal(countryName)));
	...
}


Note: We must be very careful about collisions. As you can see, we need to keep Country_.name. There cannot be two static imports for the name.

Querydsl

The Querydsl framework uses a type-safe approach by default. Therefore, we don't need to do anything in order to use it.

Generated Metamodel

The Querydsl produces classes with the "Q" prefix. In our case, we have QCountry class, now with all the attributes and static variable country for simplified usage.

Java
 
/**
 * QCountry is a Querydsl query type for Country
 */
@Generated("com.querydsl.codegen.DefaultEntitySerializer")
public class QCountry extends EntityPathBase<Country> {

    private static final long serialVersionUID = 1155880497L;

    public static final QCountry country = new QCountry("country");

    public final ListPath<com.github.aha.sat.jpa.city.City, com.github.aha.sat.jpa.city.QCity> cities = this.<com.github.aha.sat.jpa.city.City, com.github.aha.sat.jpa.city.QCity>createList("cities", com.github.aha.sat.jpa.city.City.class, com.github.aha.sat.jpa.city.QCity.class, PathInits.DIRECT2);

    public final NumberPath<Long> id = createNumber("id", Long.class);

    public final StringPath name = createString("name");

    public QCountry(String variable) {
        super(Country.class, forVariable(variable));
    }

    public QCountry(Path<? extends Country> path) {
        super(path.getType(), path.getMetadata());
    }

    public QCountry(PathMetadata metadata) {
        super(Country.class, metadata);
    }

}


Usage

We can use the generated metamodel in several similar ways as well.

Variable

The official documentation recommends using a variable (see line 2).

Java
 
public List<Country> findAllCountriesBy(@NonNull String cityName, @NonNull String cityState) {
	var city = QCity.city;
	return new JPAQuery<Country>(em)
			.select(city.country)
			.from(city)
			.where(city.name.like(cityName)
					.and(city.state.like(cityState)))
			.fetch();
}


See Querying JPA for more information.

Full Path

Another option is to use it directly with a full path (see QCity.city).

Java
 
public List<Country> findAllCountriesBy(@NonNull String cityName, @NonNull String cityState) {
	return new JPAQuery<Country>(em)
			.select(QCity.city.country)
			.from(QCity.city)
			.where(QCity.city.name.like(cityName)
					.and(QCity.city.state.like(cityState)))
			.fetch();
}


Warning: This approach is ok unless we need to use another root for the same entity.

Static Import

The last option, my preferred one, is using a static import. This way our code looks similar to SQL.

Java
 
import static com.github.aha.sat.jpa.city.QCity.city;

public List<Country> findAllCountriesBy(@NonNull String cityName, @NonNull String cityState) {
	return new JPAQuery<Country>(em)
			.select(city.country)
			.from(city)
			.where(city.name.like(cityName)
					.and(city.state.like(cityState)))
			.fetch();
}


Conclusion

This article has covered what the JPA metamodel is and how to use it in our project. First, the metamodel for JPA Criteria was explained. Next, we explained the metamodel usage with the Querydsl framework. Both technologies have several ways to use the generated metamodel. It's up to the developer's preference which one fits best. 

The complete source code presented above is available in my GitHub repository.

Metadata

Opinions expressed by DZone contributors are their own.

Related

  • Metadata, Not Data Volume, Is the Real Bottleneck in Modern Data Lakes
  • Building Unified, Access-Aware Search in a Data Mesh
  • From Data Lakes to Intelligence Lakes: Augmenting Apache Iceberg With Generative AI Metadata on AWS
  • Stabilizing ETL Pipelines With Airflow, Presto, and Metadata Contracts

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