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 Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
  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.

Arnošt Havelka user avatar by
Arnošt Havelka
CORE ·
Oct. 17, 22 · Tutorial
Like (3)
Save
Tweet
Share
5.33K 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.

Popular on DZone

  • How to Deploy Machine Learning Models on AWS Lambda Using Docker
  • DevSecOps Benefits and Challenges
  • The Real Democratization of AI, and Why It Has to Be Closely Monitored
  • 13 Code Quality Metrics That You Must Track

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

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

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: