When building data-driven applications with Spring, you often need to perform complex database operations that go beyond the capabilities of Spring Data JPA‘s built-in methods.
This is where the @Query annotation comes in – a powerful tool that allows you to define custom queries to retrieve or manipulate data in your database.
In this tutorial, you’ll learn how to use the @Query annotation to define custom queries in Spring Data JPA, covering the basics, query types, and advanced features.
By the end of this guide, you’ll be able to write custom queries that simplify your data access layer and improve your application’s performance.

Basics of the `@Query` Annotation

To effectively utilize the @Query annotation in your Spring Data JPA project, it’s important to understand its fundamentals.

@Query Annotation Syntax

@Query annotation is applied over a method.
So, to use it, you need to write a method in your Spring Data JPA repository interface where you can apply this annotation.
This annotation will typically extend from Spring Data JPA’s JpaRepository or CrudRepository interface.

The basic syntax of the @Query annotation is as follows

@Query("your_query_here")

In this syntax, “your_query_here” is replaced with your custom query.

This custom query can be either JPQL (Java Persistence Query Language) or a native SQL query written inside double quotes.

@Query Annotation Example

Suppose you want to fetch a list of users with a specific role.
If there is a field role in your User entity, then you can simply create a method in your Spring data JPA repository as below

public interface UserRepository extends JpaRepository<User, Long> { 
  List<User> findByEmail(String email); 
}

But what if you want to find a User based on some complex criteria such as a user whose name is greater than 6 characters.
In such cases, you need to write a custom query and so @Query annotation comes handy.

public interface UserRepository extends JpaRepository<User, Long> { 

  @Query("SELECT u FROM User u WHERE u.name = :name AND LENGTH(u.name) > 6")
  List<User> findUserByNameWithLengthGreaterThan(@Param("name") String name);
}

Query Types Supported by `@Query`

@Query annotation provides a flexible way to define custom queries in Spring Data JPA.
One of the key aspects of this annotation is its support for different query types.

You can use @Query annotation to define various types of queries, including:

  • JPQL (Java Persistence Query Language) queries
  • Native queries
  • Update queries
  • Delete queries
  • Procedure calls

Any of these query types can be used to define custom queries that meet your specific data access needs.

Query TypeDescription
JPQLJava Persistence Query Language, used to define queries that are independent of the underlying database
NativeDatabase-specific queries, used to leverage database-specific features
UpdateQueries that modify data in the database
DeleteQueries that delete data from the database
Procedure callsQueries that call stored procedures in the database

Overview of Supported Query Types (JPQL, Native Queries, etc.)

Even though the @Query annotation supports various query types, it’s important to understand the differences between them.
JPQL queries are platform-independent, while native queries are database-specific.
Update and delete queries modify data, whereas procedure calls execute stored procedures.

  • JPQL queries are useful when you need to define queries that are independent of the underlying database
  • Native queries are useful when you need to leverage database-specific features
  • Update and delete queries are useful when you need to modify data in the database
  • Procedure calls are useful when you need to execute stored procedures in the database
  • Thou should choose the appropriate query type based on your specific data access needs
Query TypeUse Case
JPQLPlatform-independent queries
NativeDatabase-specific features
UpdateData modification
DeleteData deletion
Procedure callsStored procedure execution

Defining Custom Queries with @Query

All Spring Data JPA applications require custom queries to retrieve specific data from the database.
@Query annotation provides a flexible way to define these custom queries.

Defining Custom Queries Using JPQL (Java Persistence Query Language)

JPQL is a powerful query language that allows you to define complex queries.
When you use @Query with JPQL, you can write queries that are similar to SQL, but with a more object-oriented approach.
You can use JPQL to define queries that retrieve specific data from your database.

Defining Custom Native Queries

Custom native queries allow you to define queries that are specific to your database vendor.
Assuming you need to execute a database-specific query, you can use nativeQuery attribute of the @Query annotation to define a native query.

Language-specific queries can be useful when you need to take advantage of database-specific features or optimize performance for a particular database vendor.
You can use native queries to perform operations that are not possible with JPQL, such as calling stored procedures or using database-specific functions.

However, keep in mind that using native queries can make your application less portable across different databases.

Using Query Parameters and Named Parameters

Custom queries often require parameters to filter or sort data. You can use query parameters and named parameters to pass values to your custom queries.
Query parameters allow you to pass values to your query at runtime, while named parameters make it easier to read and maintain your queries.
You can also use named parameters to avoid SQL injection attacks by ensuring that user input is properly escaped.

Defining query parameters and named parameters is important when you need to execute the same query multiple times with different values.
You can use @Param annotation to define named parameters and pass values to your query using the @Query annotation.

Example of a Custom Query with Multiple Parameters

One common scenario is when you need to filter data based on multiple criteria.
You can define a custom query with multiple parameters using the @Query annotation.

To illustrate this, let’s consider an example where you need to retrieve all users with a specific first name and last name

public interface UserRepository extends JpaRepository<User, Long> { 
  @Query("SELECT u FROM User u WHERE u.firstName = :firstName AND u.lastName = :lastName") 
  List findByFirstNameAndLastName(@Param("firstName") String firstName, @Param("lastName") String lastName); 
}

In this example, the findByFirstNameAndLastName() method takes two parameters, firstName and lastName, which are used to filter the results of the query.

With multiple parameters, you can define queries that are more dynamic and flexible.

Advanced Features of `@Query`

@Query annotation offers several advanced features that can help you create more complex and dynamic queries.
In this section, we’ll explore some of these features and how you can use them to improve your data access layer.

Here are some advanced features of `@Query`:

  1. Pagination and Sorting
  2. Projections (Selecting Specific Columns)
  3. Dynamic Queries (Using SpEL Expressions)
FeatureDescription
Pagination and SortingAllows you to limit the number of results returned and sort them in a specific order.
Projections (Selecting Specific Columns)Enables you to select specific columns from a table, reducing the amount of data transferred.
Dynamic Queries (Using SpEL Expressions)Allows you to create dynamic queries based on runtime conditions, making your queries more flexible.

Using @Query with Pagination and Sorting

With pagination and sorting, you can limit the number of results returned and sort them in a specific order.
This is particularly useful when working with large datasets.
You can use the Pageable interface in Spring Data JPA to specify the page size and sorting criteria.

Suppose you want to find all employees and sort them by their hire date, returning results in pages.
Below example shows how this can be achieved with custom query and @Query annotation.

public interface EmployeeRepository extends JpaRepository<Employee, Long> {

    @Query("SELECT e FROM Employee e ORDER BY e.hireDate")
    Page<Employee> findAllEmployeesWithPagination(Pageable pageable);
}

Using @Query with Projections

Projections allow you to select specific columns from a table.

Querying specific columns can reduce the amount of data transferred and reduce the amount of data transferred over the network, improving performance.
Here’s an example of how you can use projections with the @Query annotation:

public interface UserRepository { 
  @Query("SELECT u.id, u.name FROM User u") 
  List<Object[]> findUserNames(); 
} 

Instead of returning the list of whole User objects, this will return only user id and name resulting in a compact data set.

Using @Query with Dynamic Queries (Using SpEL Expressions)

Specific runtime conditions can be used to create dynamic queries.
Spring Data JPA supports SpEL (Spring Expression Language) expressions, which allow you to create dynamic queries based on runtime conditions.
This makes your queries more flexible and adaptable to changing requirements.

Here’s an example of how you can use dynamic queries with the @Query annotation

 public interface UserRepository { 
  @Query("SELECT u FROM User u WHERE u.name = :#{#username}") 
  List findUsersByUsername(@Param("username") String username); 
} 

Another benefit of using SpEL expressions is that you can externalize query logic, making it easier to maintain and update your queries.
You can also use SpEL expressions to create reusable queries that can be applied to different scenarios.

Best Practices and Performance Considerations

Now that you’ve learned how to define custom queries using the `@Query` annotation, it’s necessary to consider best practices and performance considerations to ensure your queries are efficient and scalable.

Tips for Optimizing Query Performance

When defining custom queries, you should aim to optimize their performance to reduce the load on your database and improve overall application performance.
Here are some tips to get you started:

  • Avoid using complex queries with multiple joins and subqueries.
  • Use indexing on columns used in WHERE and JOIN clauses.
  • Limit the amount of data retrieved by using pagination and filtering, and avoid using SELECT * when possible.
  • After optimizing your queries, test them thoroughly to ensure they’re performing as expected.

Best Practices for Using `@Query` in Large-Scale Applications

Best practices for using @Query in large-scale applications include keeping your queries simple and focused on a specific task, using meaningful names for your queries, and documenting your queries with comments.
This makes it easier for other developers to understand the purpose and functionality of your queries.

Performance is a critical aspect of large-scale applications.
When using @Query in such applications, it’s extremely important to consider the performance implications of your queries.
This includes using efficient query types, such as JPQL, and optimizing your database schema to support your queries.

Using @Query without considering the underlying database schema can lead to performance issues.
Using complex queries without proper indexing can result in slow query execution times.
Using @Query without testing and optimizing your queries can lead to unexpected performance issues in production.

Summing up

The guide has explained how to use Spring Data JPA’s @Query annotation to define custom queries in your data access layers.
You’ve learned the basics of the @Query annotation, the different query types it supports, and how to define custom queries using JPQL and native queries.
Additionally, you’ve explored advanced features such as pagination, sorting, and dynamic queries.
By following these best practices and performance considerations, you’re now equipped to write efficient and effective custom queries in your Spring Data JPA applications.