A deep dive into Spring Data Specifications

Understand how to write Queries with Specification and use JPA Metamodel in Spring Boot

Md. Mukitul Islam Ratul
Javarevisited
5 min readJun 2, 2023

--

When it comes to dynamic querying in a database using Spring, there are several approaches that can be considered — Spring Data Specifications, Criteria API, Query by Example (QBE), Native Queries, Named Queries, and Querydsl. Among these approaches, this article will explore Spring Data Specifications — a powerful feature in the Spring Data framework that allows developers to define complex database queries using a fluent API.

Spring Data Specifications

Specifications in a Nutshell

Spring Data Specifications is a part of the Spring Data JPA module, built on top of the Criteria API. When building a criteria query using the Criteria API, developers are typically required to construct and manage Root, CriteriaQuery, and CriteriaBuilder objects on their own. However, Spring Data Specifications aim to simplify the developer experience. Developers only need to implement the Specification interface, allowing them to construct atomic predicates and combine them to create complex dynamic queries.

Setting Up The Target

Let’s understand this through examples. Suppose there is a database table that contains information on various products. For simplicity, let’s consider that the table has four columns — Id, productName, productType, and price. This table holds information about three different types of products — Electronics, Fashion, and Home Decor.

Let’s consider a scenario where a developer needs to create an API capable of serving product information for the following queries:

  1. Retrieve all electronic products with prices greater than 500.
  2. Retrieve all home decor products with prices ranging from 200 to 1000.
  3. Retrieve all fashion and home decor products sorted by price in ascending order.

In this scenario, the developer aims to implement an API that can handle these specific queries efficiently.

Coding Part

Let’s proceed to the coding part. However, it is assumed that you already possess knowledge of how to create a Spring Boot application that can expose REST APIs and establish a connection with a database.

Firstly create an entity class for Product, you can create the entity by using the following code —

An enum ProductType is there in the entity class, so create an enum like this —

The next step is to create a repository that extends JpaRepository and also JpaSpecificationExecutor to enable specification-based querying. After doing so, your repository class will look like this —

In this step, let's develop the service class. The initial task is to decide on the structure of the QueryDto, which will hold the necessary information for the query. Once the QueryDto is defined, the next step is to implement the method responsible for creating the specification. This method will construct the dynamic query based on the provided criteria.

Here is an example of QueryDto -

Now create a ProductService class, your class may look like this for the given scenario -

Explanation, specificationForGettingAllProduct(QueryDto queryDto) — This method constructs and returns a Specification object for querying products based on the provided QueryDto object. It uses a lambda expression with three parameters: root, query, and criteriaBuilder. Inside the lambda expression, it performs the following actions —

a) Calls the getPredicateList() method to retrieve a list of predicates (conditions) based on the QueryDto object, root, and criteriaBuilder.

b) Calls the getOrderList() method to retrieve a list of sorting orders based on the QueryDto object, root, and criteriaBuilder. After getting the order list, it sets the retrieved order list to the query using query.orderBy(orderList).

In the end, returns the final Specification object that combines the predicates using criteriaBuilder.and(predicateList.toArray(new Predicate[0])).

Till this point, the specification is ready for dynamic querying for the given scenarios. You can test the getAllProduct() method of ProductService class by writing a unit test or by exposing the method via REST API.

Adding Metamodel

When writing a criteria query, referencing entity classes and their attributes often involves providing attribute names as strings. However, this approach has several downsides. Firstly, it requires looking up the names of entity attributes, which can be cumbersome. Additionally, if a column name is changed later in the project lifecycle, each query using that name would need to be refactored.

To overcome these drawbacks, the JPA Metamodel was introduced by the community. It provides a solution by offering static access to the metadata of managed entity classes. By utilizing the JPA Metamodel, developers can avoid hardcoding attribute names as strings, instead using the static metamodel classes generated by the JPA provider. This approach provides compile-time safety and reduces the risk of errors caused by attribute name changes or misspellings.

By leveraging the JPA Metamodel, developers can write more maintainable and robust criteria queries that are less prone to errors stemming from attribute name modifications or typos.

Firstly, you need to add a dependency into your pom.xml for adding metamodel -

The next step is to set up the annotation processor, in your IntelliJ IDE open the Annotation Processors tab from the Settings (ctrl+alt+s) window. Add annotation processor name — org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor

A reference image is given below —

After completing all these configurations, need to build this project, and after that metamodel class Product_.java is ready to be used. Note, need to add the target/generated-classes folder to the classpath of the IDE, as by default, the classes will be generated in this folder only. Generated metamodel will look like this —

Now rewrite the specification using metamodel —

That's all from this blog, explore more to learn more about specifications.

In conclusion, utilizing Spring Data Specifications and the JPA Metamodel in your projects can greatly enhance the flexibility, maintainability, and robustness of your data access layer. By harnessing the power of specifications, you can dynamically construct complex queries, enabling dynamic filtering and sorting based on user-defined criteria. This approach offers a clean and expressive way to build queries while keeping your codebase concise and readable.

Furthermore, leveraging the JPA Metamodel provides static access to entity metadata, eliminating the need to hardcode attribute names as strings. This not only enhances compile-time safety but also reduces the risk of errors due to attribute name changes or misspellings. The JPA Metamodel enables developers to write more reliable and future-proof code, as it automatically reflects changes made to entity classes, ensuring query integrity throughout the project lifecycle.

By combining Spring Data Specifications and the JPA Metamodel, you can build sophisticated data access layers that are more maintainable, adaptable, and resistant to errors. This powerful combination empowers developers to efficiently handle complex querying requirements while reducing the effort required for query refactoring and increasing overall code quality.

--

--

Md. Mukitul Islam Ratul
Javarevisited

Software Engineer, Interested In — Java | JavaScript | Spring Boot | React | Containerization | Microservice Development | Graph Theory | Data Mining