How to use QueryDSL with Hibernate


Take your skills to the next level!

The Persistence Hub is the place to be for every Java developer. It gives you access to all my premium video courses, monthly Java Persistence News, monthly coding problems, and regular expert sessions.


QueryDSL is a popular framework that provides you a Java API to create queries for relational databases and other data stores. If you’re familiar with JPA’s Criteria API, you might think that it does the same as QueryDSL and that you don’t need to learn about another framework. But I recommend you give QueryDSL a try. It’s not as verbose as the Criteria API, and your code will look very similar to the SQL statement you want to execute. That makes QueryDSL a lot easier to use.

QueryDSL provides you a type-safe, fluent API to create SQL-like queries in Java. Due to static typing and the usage of a generated model representing your entity classes or database tables, these queries are less error-prone and much easier to refactor than the typical String-based queries required by JPA and Hibernate. It also integrates very well with Hibernate so that you can use them together.

Generating your Model

QueryDSL’s model follows the same idea as the JPA metamodel. At build-time, an annotation processor scans your entity classes. It generates a model class for each of them, which you can use to create your queries.

To add the annotation processor to your project, you need to add a dependency to querydsl-apt to your pom.xml file.

<dependency>
	<groupId>com.querydsl</groupId>
	<artifactId>querydsl-apt</artifactId>
	<version>${querydsl.version}</version>
	<scope>provided</scope>
</dependency>

QueryDSL’s annotation processor then generates a class for each entity. Each class is located in the same package as the entity class and adds the prefix “Q” to its name. The generated class has an attribute for each entity attribute. We will use these later in this article to reference entities and their attributes while defining our queries.

/**
 * QChessPlayer is a Querydsl query type for ChessPlayer
 */
@Generated("com.querydsl.codegen.EntitySerializer")
public class QChessPlayer extends EntityPathBase<ChessPlayer> {

    private static final long serialVersionUID = -1087485884L;

    public static final QChessPlayer chessPlayer = new QChessPlayer("chessPlayer");

    public final DatePath<java.time.LocalDate> birthDate = createDate("birthDate", java.time.LocalDate.class);

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

    public final SetPath<ChessGame, QChessGame> gamesBlack = this.<ChessGame, QChessGame>createSet("gamesBlack", ChessGame.class, QChessGame.class, PathInits.DIRECT2);

    public final SetPath<ChessGame, QChessGame> gamesWhite = this.<ChessGame, QChessGame>createSet("gamesWhite", ChessGame.class, QChessGame.class, PathInits.DIRECT2);

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

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

    public final NumberPath<Integer> version = createNumber("version", Integer.class);

    public QChessPlayer(String variable) {
        super(ChessPlayer.class, forVariable(variable));
    }

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

    public QChessPlayer(PathMetadata metadata) {
        super(ChessPlayer.class, metadata);
    }

}

Integrating Hibernate and QueryDSL

QueryDSL not only supports JPA and Hibernate. It supports multiple backends and provides a separate module for each of them. The one that integrates QueryDSL with JPA is called querydsl-jpa. You need to add it as a dependency to your project.

<dependency>
	<groupId>com.querydsl</groupId>
	<artifactId>querydsl-jpa</artifactId>
	<version>${querydsl.version}</version>
</dependency>

Reading Entities with QueryDSL

After you generated the model and added QueryDSL’s JPA integration, you can use the model classes and QueryDSL’s JPAQuery class to define your queries. One of the main benefits of this approach is that the provided API is very similar to the generated SQL statement.

Creating a Simple Query

Before creating your query, you need to instantiate a JPAQuery object. When doing that, you have to provide a reference to the current EntityManager as a constructor parameter. That integrates QueryDSL with your current Hibernate Session and its database transaction.

JPAQuery<ChessPlayer> query = new JPAQuery<>(em);
QChessPlayer qChessPlayer = QChessPlayer.chessPlayer;
ChessPlayer chessPlayer = query.select(qChessPlayer)
							   .from(qChessPlayer)
							   .where(qChessPlayer.firstName.eq("Magnus"))
							   .fetchOne();

In the next step, you can then use the methods of the JPAQuery class to define your query. As you can see in the code snippet, the method’s names are self-explanatory, and the Java code looks very similar to your SQL statement.

In this case, I call the select method with a reference to a QChessPlayer object to create a query that selects ChessPlayer entity objects. QueryDSL uses Hibernate to execute the query and map the result. Due to that, the returned entity objects are in lifecycle state managed. You can use them in the same way as any entity objects loaded via a JPQL query or the EntityManager‘s find method.

I use that same QChessPlayer object to define the FROM and WHERE clause of the query. In this example, I want to select all players with the firstName “Magnus”. The firstName attribute of the QChessPlayer class is of type StringPath. It’s one of QueryDSL’s model classes. It provides type-specific methods to create expressions that you can use in various parts of your query. In this case, I use it to create an equals expression that compares the firstName attribute of a ChessPlayer with the provided String.

And after you defined your query, you can call the fetch or fetchOne method to execute the query. When calling the fetchOne method, your query has to return none or one result. The fetch method returns the result as a List.

Increasing the Complexity

As you saw in the previous section, implementing an SQL query using QueryDSL isn’t complicated. And that doesn’t change if your SQL statement gets more complex. The names of the methods provided by the JPAQuery class are self-explanatory and often match the SQL keyword.

Here you can see a query that uses multiple JOIN clauses and WHERE expressions to select a chess game played by 2 given players.

JPAQuery<ChessGame> query = new JPAQuery<>(em);

QChessGame qChessGame = QChessGame.chessGame;
QChessPlayer whitePlayer = new QChessPlayer("whitePlayer");
QChessPlayer blackPlayer = new QChessPlayer("blackPlayer");

ChessGame game = query.from(qChessGame)
					  .innerJoin(qChessGame.playerWhite, whitePlayer)
					  .innerJoin(qChessGame.playerBlack, blackPlayer)
					  .where(whitePlayer.lastName.eq("Caruana")
							 .and(blackPlayer.lastName.eq("van Foreest")))
					  .fetchOne();

You can see in the code snippet that I used 2 QChessPlayer objects. One represents the player with the white pieces and the other one the player with the black pieces. That is necessary to ensure that QueryDSL uses different aliases for the 2 JOIN clauses so that we end up with a query with 2 independent JOIN clauses.

Updating and Deleting Records using QueryDSL

You can use QueryDSL’s JPAQueryFactory class to define SQL DELETE and UPDATE statements. You can do that in almost the same way we created the query statements in the previous section.

JPAQueryFactory queryFactory = new JPAQueryFactory(em);

QChessGame qChessGame = QChessGame.chessGame;      

long deleted = queryFactory.delete(qChessGame)
						   .where(qChessGame.round.eq(4))
						   .execute();

But I don’t recommend doing that if you use QueryDSL with Hibernate or any other JPA implementation. It’s much easier to implement these operations using entity objects. Hibernate then also manages first- and second-level caches. These don’t get updated if you implement your update or removal operations using QueryDSL. So, better keep it simple and perform the required changes on your entity objects.

Conclusion

For some use cases, you need to define your query dynamically at runtime. You can do that using JPA’s Criteria API. But most developers (myself included) complain that it’s too verbose, hard to read, and obscuring the generated SQL statement.

QueryDSL provides an API that’s much closer to the defined SQL statement and integrates well with Hibernate. Thanks to its annotation processor, you can define your queries based on a set of model classes. Each of them is are strongly typed and represents one of your entities. That avoids almost all Strings in your code, makes it much easier to read, and increases type safety.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.