|

How to generate DAOs and queries 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.


Executing a query with Hibernate requires several lines of repetitive boilerplate code, and many developers have complained about that for years. You have to instantiate a Query object and set all bind parameter values before you can finally execute the query. Since version 6.3.1, Hibernate has solved this with an improved metamodel generator that provides you with DAO implementations that handle that for you.

You define a DAO as an interface with a few annotated method definitions. Here, you can see a simple example.

public interface ChessGameDao {
    
    // Generate the required code to execute the provided statement as a HQL query
    @HQL("SELECT g FROM ChessGame g LEFT JOIN FETCH g.moves WHERE g.playerWhite = :playerWhite")
    List<ChessGame> findGamesWithMovesByPlayerWhite(String playerWhite);
}

Based on this interface, Hibernate’s metamodel generator creates the ChessGameDao_ class when you build your project.

@StaticMetamodel(ChessGameDao.class)
@Generated("org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
public abstract class ChessGameDao_ {

	
	/**
	 * Execute the query {@value #FIND_GAMES_WITH_MOVES_BY_PLAYER_WHITE_String}.
	 *
	 * @see com.thorben.janssen.dao.ChessGameDao#findGamesWithMovesByPlayerWhite(String)
	 **/
	public static List<ChessGame> findGamesWithMovesByPlayerWhite(@Nonnull EntityManager entityManager, String playerWhite) {
		return entityManager.createQuery(FIND_GAMES_WITH_MOVES_BY_PLAYER_WHITE_String, ChessGame.class)
				.setParameter("playerWhite", playerWhite)
				.getResultList();
	}

	static final String FIND_GAMES_WITH_MOVES_BY_PLAYER_WHITE_String = "SELECT g FROM ChessGame g LEFT JOIN FETCH g.moves WHERE g.playerWhite = :playerWhite";

}

As you can see in the code snippet, the generated class doesn’t rely on dependency injection or any other services provided by a container. Each query method expects an instance of your current EntityManager as a method parameter and uses it to execute the defined query. So, you can use these generated methods in every Java SE or Jakarta EE application.

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

List<ChessGame> games = ChessGameDao_.findGamesWithMovesByPlayerWhite(em, "Anish Giri");

em.getTransaction().commit();
em.close();

OK, let me show you how to activate Hibernate’s metamodel generator before we get into more details about defining a DAO and Hibernate’s support for different kinds of queries.

Activate Hibernate’s metamodel generator

Hibernate’s metamodel generator is an annotation processor that you have to add to your build configuration. Here, you can see an example configuration for Gradle and Maven. Please check Hibernate’s documentation if you’re using a different build tool.

Gradle:

dependencies {
    ...
    implementation("org.hibernate.orm:hibernate-core:6.3.1.Final")
    implementation("jakarta.annotation:jakarta.annotation-api:2.1.1")

    
    annotationProcessor("org.hibernate.orm:hibernate-jpamodelgen:6.3.1.Final")
}

Maven:

<?xml version="1.0" encoding="UTF-8"?>
<project>
	...

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.11.0</version>
				<configuration>
					<annotationProcessorPaths>
						<path>
							<groupId>org.hibernate.orm</groupId>
							<artifactId>hibernate-jpamodelgen</artifactId>
							<version>${hibernate.version}</version>
						</path>
					</annotationProcessorPaths>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

After you add Hibernate’s metamodel generator as an annotation processor to your build process, it will generate implementing classes for your DAO interfaces and all other parts of the metamodel during each build. You can find your DAO implementations under target/generated-sources/annotations in the same package as your DAO interface definition.

Define your DAOs

The definition of a DAO is straightforward if you’re using Hibernate’s metamodel generator. You only have to create an interface, add a method for every query you want to define, and annotate it with an @HQL, @SQL or @Find annotation. Each of these annotations enables you to define a different kind of query. Let’s take a closer look at each of them.

public interface ChessGameDao {
 ... }

HQL queries

HQL is Hibernate’s proprietary query language. It’s an extension of JPA’s JPQL language.

Define a simple HQL query method

You can define a DAO method that executes an HQL query by annotating your method with an @HQL annotation and providing the HQL statement. Hibernate expects a method parameter with the same name and a compatible type for every named bind parameter in your query. It uses it to set the bind parameter’s value when executing the query.

public interface ChessGameDao {
    
    // Generate the required code to execute the provided statement as a HQL query
    @HQL("SELECT g FROM ChessGame g LEFT JOIN FETCH g.moves WHERE g.playerWhite = :playerWhite")
    List<ChessGame> findGamesWithMovesByPlayerWhite(String playerWhite);
}

Hibernate’s metamodel generator creates an implementation class for your DAO interface during the next build. So, in this example, it generates a ChessGameDao_ class with the static method List findGamesWithMovesByPlayerWhite(@Nonnull EntityManager entityManager, String playerWhite). As you can see, the parameter list of the generated method differs from the one defined by the ChessGameDao interface. It adds a parameter of type EntityManager as the 1st parameter.

In your business code, you can then instantiate your own EntityManager or use dependency injection to get the current instance from your container and provide it as an input parameter. The generated method implementation uses that EntityManager instance to create a query for the defined statement and execute it.

List<ChessGame> games = ChessGameDao_.findGamesWithMovesByPlayerWhite(em, "Anish Giri");

Define an HQL query with pagination and ordering

Hibernate’s metamodel generator also supports ordering and pagination of your query result. You can define them by adding a parameter of type Page or Order to your method definition.

@HQL("FROM ChessGame g WHERE g.playerWhite = :playerWhite")
List<ChessGame> findGamesByPlayerWhitePaged(String playerWhite, Page page, Order<? super ChessGame>... order);

The Page and Order classes provide a set of static methods that make them easy to instantiate when you call the generated method implementation.

List<ChessGame> games = ChessGameDao_.findGamesByPlayerWhitePaged(em, "Anish Giri", Page.page(10, 0), Order.asc(ChessGame_.date));

The usage and effect of the Order parameter are self-explanatory. You can use it to define the ascending or descending ordering of the query result based on an entity attribute.

The Page parameter provides a simplified way to define the pagination of your query result. The idea is simple. You define the size of each page and which page you want to get. Based on this information, Hibernate then calculates the index of the 1st record and the number of records the query shall return and uses that to paginate the result of the generated SQL query.

14:05:03,447 DEBUG [org.hibernate.SQL] - 
    select
        cg1_0.id,
        cg1_0.date,
        cg1_0.playerBlack,
        cg1_0.playerWhite,
        cg1_0.round,
        cg1_0.version 
    from
        ChessGame cg1_0 
    where
        cg1_0.playerWhite=? 
    order by
        cg1_0.date 
    offset
        ? rows 
    fetch
        first ? rows only

Define an HQL query returning records

Entities are, of course, not the only projection you can use in your JPQL queries. As I explained in my Hibernate performance tuning tips, unmanaged DTOs are the better projection if your business code doesn’t change the retrieved information. They avoid any overhead, and you can adjust them to the needs of the business code.

Java records are a simple and efficient way to implement an unmanaged DTO projection.

public record ChessGameRecord(Long id, String playerWhite, String playerBlack) {
}

If you want to use the record as your query’s projection, you have to use it as the return type of your DAO’s query method. Your query statement has to select the required information in the order of your record’s constructor parameter. So, to use the previously shown ChessGamesRecord as your query’s projection, your query has to select the id, playerWhite, and playerBlack attributes of the ChessGame entity.

public interface ChessGameDao {
    @HQL("SELECT g.id, g.playerWhite, g.playerBlack FROM ChessGame g WHERE g.playerWhite = :playerWhite")
    List<ChessGameRecord> findGameRecordsByPlayerWhite(String playerWhite);
}

Hibernate then executes the specified query and maps each record of the query result to a ChessGameRecord object.

SQL queries

Hibernate’s generated DAOs also support native SQL queries. You define them by annotating your query method with an @SQL annotation and providing a native SQL statement.

public interface ChessGameDao {
    @SQL("SELECT * FROM ChessGame g WHERE g.playerWhite = :playerWhite")
    List<ChessGame> findNativeGamesByPlayerWhite(String playerWhite);
}

Based on this information, Hibernate’s metamodel generator provides you with ChessGameDao_ class with the findNativeGamesByPlayerWhite(@Nonnull EntityManager entityManager, String playerWhite) method. As you can see in the following code snippet, that method uses the provided EntityManager to instantiate a native query for the defined statement, sets all bind parameters and executes it.

@StaticMetamodel(ChessGameDao.class)
@Generated("org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
public abstract class ChessGameDao_ {

	
	/**
	 * Execute the query {@value #FIND_NATIVE_GAMES_BY_PLAYER_WHITE_String}.
	 *
	 * @see com.thorben.janssen.dao.ChessGameDao#findNativeGamesByPlayerWhite(String)
	 **/
	public static List<ChessGame> findNativeGamesByPlayerWhite(@Nonnull EntityManager entityManager, String playerWhite) {
		return entityManager.createNativeQuery(FIND_NATIVE_GAMES_BY_PLAYER_WHITE_String, ChessGame.class)
				.setParameter("playerWhite", playerWhite)
				.getResultList();
	}

	static final String FIND_NATIVE_GAMES_BY_PLAYER_WHITE_String = "SELECT * FROM ChessGame g WHERE g.playerWhite = :playerWhite";
}

Find methods

Hibernate generates a basic query statement for you if you annotate a method of your DAO interface with Hibernate’s @Find annotation.

public interface ChessGameDao {

    @Find
    List<ChessGame> justSomeRandomMethodName(String playerWhite, String playerBlack);
}

In contrast to other frameworks, Hibernate ignores the name of the annotated method. It instead uses the return type of the method to determine the type of entity you want to select. And it generates a WHERE class that compares each provided bind parameter with an entity attribute of the same name for equality.

So, for the previously shown method List<ChessGame> justSomeRandomMethodName(String playerWhite, String playerBlack), Hibernate executes the following SQL statement.

14:42:38,136 DEBUG [org.hibernate.SQL] - 
    select
        cg1_0.id,
        cg1_0.date,
        cg1_0.playerBlack,
        cg1_0.playerWhite,
        cg1_0.round,
        cg1_0.version 
    from
        ChessGame cg1_0 
    where
        cg1_0.playerWhite=? 
        and cg1_0.playerBlack=?

Conclusion

Starting with version 6.3.1, Hibernate’s metamodel generator can generate a basic implementation for an interface-based DAO definition. Such a DAO definition consists of a simple interface with at least 1 method annotated with @HQL, @SQL, or @Find. For each of these interfaces, the metamodel generator creates a class that has the same name as the interface with an added underscore. So, for the ChessGameDao interface, Hibernate generates the ChessGameDao_ class.

For each method annotated with @HQL or @SQL, the generator creates a method with the required boilerplate code to execute the provided JPQL or native SQL statement.

And if you annotate a method with Hibernate’s @Find annotation, the metamodel generator creates the query statement and the required code to execute it. The method has to return entity objects, and Hibernate expects that for each method parameter, the entity class has a mapped attribute with a matching name. It then generates a query returning the entity objects that match all provided bind parameters.

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.