Writing type-safe JPA queries with QueryDSL

I became a fan of type-safe queries long time ago (unless I need plain SQL queries via JDBC). What are type-safe queries and how easily they can be maintained? Criteria, QueryDSL, JOOQ? This post is not about which framework is the best one. It’s about how easily implement JPA queries using QueryDSL.

I believe JOOQ is a great framework for plain SQL based environments, however my main work is based on JPA environments, so I chose QueryDSL. Why not the standard - Criteria? Due to it’s complexity. I always try to make things simple, and using Criteria is not the case.

JPQL is great to develop things fast, but maintenance is slow when you change your entity structure. And you do not see the issues unless you know the code by heart. But what if that code is not written by you? Unit tests - true, but sometimes the life is not so easy as we would like it to be :) In that case QueryDSL gives you some advantage to trace the appearing query issues easily during compile time. It does not solve all your issues, but at least it minimizes them :)

Concerns: Tim has left Mysema, however I believe he will continue to support QueryDSL project!

Preconditions

You need this environment setup in order to successfully complete the provided guide:

Issue

The main issue here is the complexity of Criteria API in order to create a JPA type-safe query. I am able to write a fast query using JPQL, but it also might be a buggy one as well. On other hand I am free to to build a type-safe query with Criteria API, but for that reason I need a CriteriaBuilder, Root and CriteriaQuery. Let’s take an example:

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<BlogEntry> cq = cb.createQuery(BlogEntry.class);
Root<BlogEntry> be = cq.from(BlogEntry.class);
cq.select(be);
TypedQuery<BlogEntry> q = em.createQuery(cq);
List<BlogEntry> allItems = q.getResultList();

Assert.assertTrue(allItems.isEmpty());

Too many things to do and remember.

Solution

With QueryDSL your things to remember are minimised and you may start doing what you were intended to do - write a query. Of course, you need to setup your environment to create a metamodel for you:

Eclipse annotation processing

Then you need to configure maven pom.xml file to add required dependencies. But that is one time work to be done for every project.

<dependency>
	<groupId>com.mysema.querydsl</groupId>
	<artifactId>querydsl-apt</artifactId>
	<version>3.6.3</version>
</dependency>
		
<dependency>
	<groupId>com.mysema.querydsl</groupId>
	<artifactId>querydsl-jpa</artifactId>
	<version>3.6.3</version>
</dependency>

Plain QueryDSL syntax

Metamodel is automatically generated for you once you have prepared your domain model. You should be able to start writing your queries :)

QBlogEntry qb = QBlogEntry.blogEntry;
Assert.assertTrue(new JPAQuery(getEntityManager()).from(qb).where(qb.title.eq("test")).fetch().exists());

Your query starts using standard methods, such as from and where and continues to grow once additional predicates are added. Of course, it grows based on your domain complexity. But at least you know what properties are available for predicate to be built.

JPAQuery Wrapper

Usually I am trying to go even further and minimize the list of possible properties, available for queries. This would allow you to minimize unexpected application behaviour of your application. Example provided below and detailed implementation is in Git repository!

Assert.assertTrue(new BlogEntryQueryImpl(getEntityManager()).title("test").list().isEmpty());

Where the BlogEntryQueryImpl implementation is as follows:

public class BlogEntryQueryImpl extends AbstractQuery<QBlogEntry, BlogEntry> {

	public BlogEntryQueryImpl(EntityManager entityManager) {
		super(entityManager, QBlogEntry.blogEntry);
	}

	public BlogEntryQueryImpl title(String title) {
		getQuery().where(getEntityPath().title.eq(title));
		return this;
	}

}

As always you need to follow the requirements and see what is the best way you need for your project.

Cons

You cannot join your entities based on some property, which is not a foreign key. And sometimes this is a really painful situation.

Alternatives

Discussions

Resources

Happy designing and coding!

vytas

Senior software engineer, software architect for java enterprise based solutions