Contact

Technology

Jun 11, 2014

What Can Querydsl Do for Me Part 1: How to Enhance and Simplify Existing Spring Data JPA Repositories

Bryan Chapman

Bryan Chapman

Default image background

WHAT IS QUERYDSL?

Querydsl is an extensive Java framework, which allows for the generation of type-safe queries in a syntax similar to SQL. It currently has a wide range of support for various backends through the use of separate modules including JPA, JDO, SQL, Java collections, RDF, Lucene, Hibernate Search, and MongoDB. In this post, I’ll be focusing on Querydsl’s support for JPA, and how it can be used to extend the already rich feature set that JPA provides.

WHY CHOOSE QUERYDSL?

While alternatives to Querydsl do exist, several of the reasons I continue to use Querydsl in my everyday work are the simplicity of adaption, the ability to consolidate a large amount of repository methods to a much simpler subset (which we’ll see in the code examples detailed below), and the type-safety it provides.  In addition to my own personal reasons behind choosing Querydsl, the creators also boast these points:

–  Code completion in an integrated development environment (IDE)—all properties, methods and operations can be expanded in your favorite Java IDE –  Almost no syntactically invalid queries allowed (type-safe on all levels) –  Domain types and properties can be referenced safely (no Strings involved!) –  Adopts better to refactoring changes in domain types –  Incremental query definition is easier

Now that you know the benefits of Querydsl, I’ll give you instructions and code examples showing how to use it in your project.

HOW TO ENABLE QUERYDSL IN YOUR EXISTING PROJECT

Before you can get started with Querydsl in your projects, you’ll first need to add the following dependencies, as well as configure your project to use the plugin settings below. This processor plugin will find all of your domain types with the Entity annotation and create query types from them automatically, which we will then use to build our queries.

Once this plugin is configured and you perform your next build, all of the generated Query types will be present in “src/main/generated”

Note: If you are using hibernate annotations in your domain types, then you should change the processor class to com.mysema.query.apt.hibernate.HibernateAnnotationProcessor instead.

DEPENDENCIES

com.mysema.querydsl Querydsl-apt com.mysema.querydsl    querydsl-jpa

MAVEN INTEGRATION

  ...   com.mysema.maven maven-apt-plugin 0.3.2       process     src/main/generated com.mysema.query.apt.jpa.JPAAnnotationProcessor         ...

GRADLE INTEGRATION

configurations { querydslapt }   dependencies { … // for the anontation processor querydslapt group: 'com.mysema.querydsl', name: 'querydsl-jpa', version: '2.8.0', classifier: 'apt-one-jar', transitive: false}//Querydsl def generatedSrcDir = 'src/main/generated' task createGeneratedSrcDir << { file(generatedSrcDir).mkdirs()} compileJava.dependsOn createGeneratedSrcDir   compileJava { options.compilerArgs << '-processor' << 'com.mysema.query.apt.jpa.JPAAnnotationProcessor' << '-s' << file(generatedSrcDir).absolutePath}   clean { delete generatedSrcDir }

BUILDING DYNAMIC QUERIES WITH SPRING DATA

Consider an employee database where many portions of your application’s query for employees are based on different sets of criteria. Using Querydsl, you can easily build these queries in a familiar syntax to that of SQL in a type-safe manner. Below are some quick examples to give you a glimpse into what Querydsl can do for you:

query.from(employee).where(manager.firstName.eq("Justin")); query.from(employee).where(employee.firstName.eq("Bryan").and(employee.lastName.eq("Chapman"))); query.from(employee).where(employee.tenure.gt(5)).and(employee.dept.eq(“Sales”);

A CLOSER LOOK

Up until this point, we haven’t seen Querydsl accomplish anything we couldn’t have easily done with straight Spring Data. Now that we have a basic understanding of what Querydsl is and how we can configure it in our own applications, let’s take a look at an existing Spring Data project and see how we can utilize Querydsl to enhance and simplify the repository and service layers.

WHERE DO I FIND THE CODE?

To retrieve the code for this example, you’ll need to install Git and then run the following command:

git clone -b b1.1

Note: Each section of the example has a corresponding branch with sample code, so be sure to change branches and follow along when instructed.

For more information on this install, take a look at my sample project here.

INITIAL SPRING DATA REPOSITORY

This example application was initially designed to hold and search bank branch locations across different banks. Here, it will be used for demonstration purposes and will be enhanced as we go through this post. This example was built using Spring Boot and was initially a simple Spring Data repository. It contains a single model, BranchLocation, which contains relevant information such as bank name, branch name, address, city, etc. Let’s focus on our BranchLocation repository now. Those of you familiar with Spring Data will find the code below familiar, as it defines a simple Spring Data repository.

public interface BranchLocationRepository extends CrudRepository<BranchLocation, Long> {   List findByBankName(String bankName); List findByBranchName(String branchName); List findByAddress(String address); List findByCity(String city); List findByState(String state); List findByZip(String zip); List findByCountry(String country); List findByPhoneNumber(String phoneNumber);}

By simply creating this interface and extending CrudRepository, we now have a repository which is capable of searching for our branch locations by any of its available fields without writing any actual method implementations.  When you execute this application, you see several of these repository search methods put to use. This is a very nice feature, especially when our entities are simple and our data filtering needs are very limited; however, this repository begins to show its weaknesses as our needs and entities grow.

WHAT HAPPENS WHEN OUR FILTERING NEEDS EXPAND?

While Spring Data fits our needs in the previous example, what happens when our searching requirements change, even though our entity is still quite simple? Imagine that we need to be able to search for a branch location based on a combination of any two fields, where both these fields match. Well, looking at our previous repository definition we obviously need to add more methods.

Time to switch branches: git checkout b1.2

public interface BranchLocationRepository … … List findByBankNameAndBranchName(String bankName, String branchName);List findByBankNameAndAddress(String bankName, String address);List findByBankNameAndCity(String bankName, String city);List findByBankNameAndState(String bankName, String state);List findByBankNameAndZip(String bankName, String zip);List findByBankNameAndCountry(String bankName, String country);List findByBankNameAndPhoneNumber(String bankName, String phoneNum);

While this snippet does not define all the methods required to meet our changing needs, it does show an interesting side effect of using Spring Data repository. It does not scale well as our searching needs increase. This snippet only enables us the ability to search by field pairs with the name of the bank as one of the options. The number of methods will continue to increase dramatically as we introduce further searching requirements. Considering the above, imagine what will happen if we required the ability to search based on three fields that all matched. Or how about four? What if all of the fields matched? It’s easy to see how this can get out of hand.

To further expand on this point, let’s explore another addition to our searching requirements above.  Previously, we considered our searching to require a match in all the fields we wished to search for. Suppose we wished to support the ability to include results that match any of the fields and values we provided as well.

Time to switch branches: git checkout b1.3

public interface BranchLocationRepository… … List findByBankNameOrBranchName(String bankName, String branchName);List findByBankNameOrAddress(String bankName, String address);List findByBankNameOrCity(String bankName, String city);List findByBankNameOrState(String bankName, String state);List findByBankNameOrZip(String bankName, String zip);List findByBankNameOrCountry(String bankName, String country);List findByBankNameOrPhoneNumber(String bankName, String phoneNumber);

Once again, this snippet only enables us the ability to search by field pairs with the name of the bank as one of the options. Imagine if this was an actual project and methods could not be excluded for the sake of time. Not only would the repository layer be flooded with search methods for every possible combination, this would also bleed into the service layer implementations and eventually all the way up to the controller layer. What happens when the model changes and additional fields are added, now that we have even more methods? Let’s put a stop to this madness—I’ll show you how Querydsl can provide this desired search functionality, with one search method in our service layer and no additional repository methods declared (aside from those provided by Querydsl).

TIME TO SAVE THE DAY, QUERYDSL!

Let’s take a peek at what our repository looks like now that we’ve configured Querydsl. Where did all our methods go? How can we possibly accomplish our searching needs without a single method written? Well, let’s see how:

Time to switch branches: git checkout b1.4

public interface BranchLocationRepository extends JpaRepository<BranchLocation, Long>, QueryDslPredicateExecutor {}

If you perform a Gradle build and investigate your source directory, you will now see a new class in src/main/generated/com/credera/querydsl called QBranchLocation. If you open this file, you will notice this class has all of the same variables as our BranchLocation, but notice they are now declared as StringPath and NumberPath. These types allow for type-safe property paths, further adding to the robustness of Querydsl implementations. This generated class will be the basis for how we build dynamic type-safe queries with a single repository method.

By allowing for the flexibility of creating dynamic queries on the fly, we are no longer required to create a specific method for every possible search combination a client may require (however, you are still free to do this).

While the QueryDslPredicateExecutor interface provides us with several methods for executing queries (which I urge you to read more into), our focus in this sample project is the method below. This method will allow for us to build a dynamic predicate using our QBranchLocation model from above.

Iterable findAll(Predicate predicate);

Let’s use a few examples to explore these terms (where bl = QBranchLocation.branchLocation);

Find all branch locations located in Dallas:

findAll(bl.city.eq(“Dallas”).and(bl.state.eq(“Texas”));

Find all Citibank branch locations in either Texas or Connecticut:

findAll(bl.branchName.eq(“CitiBank”).and((bl.state.eq(“Texas”).or(b1.state.eq(“Connecticut”)))

Find all Bank of Texas branches named Downtown Penn in Texas:

findAll(bl.bankName.eq("Bank of Texas").and(bl.branchName.eq("Downtown Penn").and(bl.state.eq("TX"))));

As you can see, we were very easily able to create dynamic queries based on our specific search needs at that current moment, even when the repository developer was not aware of our requirements. By not coupling a single method to each search requirement (as we saw with our Spring Data implementation), we can open up the flexibility to search on any combination of terms as necessary without needing to update our repository layer.

WHAT’S NEXT?

Thank you for following along on my first installment of the “What Can Querydsl Do for Me” series. In my next post, I’ll delve deeper into Querydsl, and how it can support your efforts of data compartmentalization, as well as deeper initialization paths.

Conversation Icon

Contact Us

Ready to achieve your vision? We're here to help.

We'd love to start a conversation. Fill out the form and we'll connect you with the right person.

Searching for a new career?

View job openings