Spring Data JPA Tutorial: Creating Database Queries With the @Query Annotation

The previous part of this tutorial described how we can create database queries from the method names of our query methods.

Although that strategy has its advantages, it has its weaknesses as well. This blog post describes how we can avoid those weaknesses by using the @Query annotation.

We will also implement a simple search function that has two requirements:

  • It must return todo entries whose title or description contains the given search term.
  • The search must be case insensitive.

Let’s get started.

Additional Reading:

If you are not familiar with Spring Data JPA, you should read the following blog posts before you continue reading this blog post:

Creating Query Methods

We can configure the invoked database query by annotating the query method with the @Query annotation. It supports both JPQL and SQL queries, and the query that is specified by using the @Query annotation precedes all other query generation strategies.

In other words, if we create a query method called findbyId() and annotate it with the @Query annotation, Spring Data JPA won’t (necessarily) find the entity whose id property is equal than the given method parameter. It invokes the query that is configured by using the @Query annotation.

Example:

Let’s assume that our repository interface looks as follows:

import org.springframework.data.repository.Repository;
 
import java.util.Optional;
 
interface TodoRepository extends Repository<Todo, Long> {
 
 	@Query("SELECT t FROM Todo t WHERE t.title = 'title'")
	public List<Todo> findById();
}

Even though the findById() method follows the naming convention that is used to create database queries from the method name of the query method, the findById() method returns todo entries whose title is 'title', because that is the query which is specified by using the @Query annotation.

Let’s find out how we can create both JPQL and SQL queries with the @Query annotation.

Creating JPQL Queries

We can create a JPQL query with the @Query annotation by following these steps:

  1. Add a query method to our repository interface.
  2. Annotate the query method with the @Query annotation, and specify the invoked query by setting it as the value of the @Query annotation.

The source code of our repository interface looks as follows:

import org.springframework.data.repository.Repository;
 
import java.util.Optional;
 
interface TodoRepository extends Repository<Todo, Long> {
 
 	@Query("SELECT t FROM Todo t WHERE t.title = 'title'")
	public List<Todo> findByTitle();
}

Creating SQL Queries

We can create a SQL query with the @Query annotation by following these steps:

  1. Add a query method to our repository interface.
  2. Annotate the query method with the @Query annotation, and specify the invoked query by setting it as the value of the @Query annotation's value attribute.
  3. Set the value of the @Query annotation’s nativeQuery attribute to true.

The source code of our repository interface looks as follows:

import org.springframework.data.repository.Repository;
 
import java.util.Optional;
 
interface TodoRepository extends Repository<Todo, Long> {
 
 	@Query(value = "SELECT * FROM todos t WHERE t.title = 'title'",
			nativeQuery=true
	)
	public List<Todo> findByTitle();
}

Let’s move on and find out how we can create a query method that fulfils the requirements of our search function.

Implementing the Search Function

This section describes how we can implement a case insensitive search function that returns all todo entries whose title or description contains the given search term.

Implementing the Search Function With JPQL

We can create the JPQL query that fulfils the requirements of our search function by following these steps:

  1. Create a query method that returns a list of Todo objects.
  2. Annotate the method with the @Query annotation.
  3. Create the JPQL query which uses named parameters and returns todo entries whose title or description contains the given search term (remember to ignore case). Set the created query as the value of the @Query annotation.
  4. Add one method parameter to the query method and configure the name of the named parameter by annotating the method parameter with the @Param annotation.

The source code of our repository interface looks as follows:

import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.query.Param;

import java.util.List;

interface TodoRepository extends Repository<Todo, Long> {

    @Query("SELECT t FROM Todo t WHERE " +
            "LOWER(t.title) LIKE LOWER(CONCAT('%',:searchTerm, '%')) OR " +
            "LOWER(t.description) LIKE LOWER(CONCAT('%',:searchTerm, '%'))")
    List<Todo> findBySearchTerm(@Param("searchTerm") String searchTerm);
}

Let's find out how we can implement our search function by using SQL.

Implementing the Search Function With SQL

We can create the SQL query that fulfils the requirements of our search function by following these steps:

  1. Create a query method that returns a list of Todo objects.
  2. Annotate the method with the @Query annotation.
  3. Create the invoked SQL query by following these steps:
    1. Create the SQL query which uses named parameters and returns todo entries whose title or description contains the given search term (remember to ignore case). Set the created query as the value of the @Query annotation's value attribute.
    2. Set the value of the @Query annotation's nativeQuery attribute to true.
  4. Add one method parameter to the query method and configure the name of the named parameter by annotating the method parameter with the @Param annotation.

The source code of our repository interface looks as follows:

import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.query.Param;

import java.util.List;

interface TodoRepository extends Repository<Todo, Long> {

    @Query(value = "SELECT * FROM todos t WHERE " +
            "LOWER(t.title) LIKE LOWER(CONCAT('%',:searchTerm, '%')) OR " +
            "LOWER(t.description) LIKE LOWER(CONCAT('%',:searchTerm, '%'))",
            nativeQuery = true
    )
    List<Todo> findBySearchTermNative(@Param("searchTerm") String searchTerm);
}

Let’s move on and find out when we should create our database queries with the @Query annotation.

When Should We Use the @Query Annotation?

The @Query annotation has the following benefits:

  • It supports both JPQL and SQL.
  • The invoked query is found above the query method. In other words, it is easy to find out what the query method does.
  • There is no naming convention for query method names.

The @Query annotation has the following drawbacks:

  • There is no support for dynamic queries.
  • If we use SQL queries, we cannot change the used database without testing that our SQL queries work as expected.

When we think about the pros and cons of the @Query annotation, it becomes clear that the query methods that use this strategy aren’t as easy to write or read as the query methods that use the query generation from the method name strategy.

However, these query methods have two crucial advantages:

  1. If we need to find out what database query is invoked by our query method, we can find the invoked query above the query method.
  2. The code that uses our query methods is easy to read because we don’t have to use long method names.

Thus, if we don’t want to use the query generation from the method name strategy, because the names of our query methods would be too long, we should use the @Query annotation.

Let’s move on and summarize what we learned from this blog post.

Summary

This blog post has taught us three things:

  • The database queries specified by using the @Query annotation precedes all other query generation strategies.
  • The @Query annotation supports both JPQL and SQL queries.
  • If we cannot create our database queries from the method names of our query methods, because the method names would be too long, we should create them by using the @Query annotation.

The next part of my Spring Data JPA tutorial describes how we can create database queries by using named queries.

P.S. You get can get the example application of this blog post from Github.

If you want to learn how to use Spring Data JPA, you should read my Spring Data JPA tutorial.
61 comments… add one
  • tin tuc Jul 16, 2015 @ 5:41

    Thanks very іnteresting blog!

    • Petri Jul 16, 2015 @ 10:50

      You are welcome!

  • TheZeroCode Oct 27, 2015 @ 15:51

    This is the first tym m reading about "Spring Data JPA" and this blog has given me crystal clear idea about the topic.
    Thanks :)

    • Petri Oct 27, 2015 @ 19:28

      You are welcome! Also, thank you for your kind words. I really appreciate them.

  • Marissa Feb 3, 2016 @ 17:38

    Your tutorials have been very helpful. I've been following along with my own tables/entities, and I'm having an issue I haven't been able to solve at this point. My table is named poc_site, and my entity class is Site. I can't use any of these as queries:

     
    "select s from Site s where s.site_name = 'Test Site'
    "select s from PocSite s where s.site_name = 'Test Site'"
    "select s from Poc_site s where s.site_name = 'Test Site'"
    

    I've been googling for how to determine the appropriate name to enter, and haven't found anything... how are these names determined here?
    Thanks!

    • Marissa Feb 3, 2016 @ 17:47

      Actually, I figured out my issue...

      • Petri Feb 3, 2016 @ 17:55

        Hi Marissa,

        I am happy to hear that you where able to solve your problem. I assume that your problem was that the site_name is the name of the database column and not the name of the entity class' field. Am I correct?

  • Venkata Feb 25, 2016 @ 7:10

    Hi Petri,
    Thank You for the blog.
    How we can give setfetchsize with @Query.
    Thanks in advance

  • Lukas Tibursky Apr 4, 2016 @ 18:29

    thanks a lot! this passage in the custom query gave me headaches before I read this: LIKE LOWER(CONCAT('%',:searchTerm, '%'))

    Now its clear!
    :)

    • Petri Apr 4, 2016 @ 20:16

      You are welcome! I am happy to hear that this blog post was useful to you.

  • Fernando Apr 29, 2016 @ 14:35

    Hey Petri, your blog is awesome. It is being very useful to me and my projects.. I want to ask you if could you make a post about calling stored procedures that returns data (for example a list of articles) with JPA (Spring or Hibernate).

    Thank you!

  • Joao May 6, 2016 @ 16:11

    Hi Petri!

    Is there a way to eager load enities wher usin native queries?

    Assume EntityA has a list of EntityB and a list of EntityC. What i want is:

    @Query(value = "SELECT a.*, b.*, c.* " +
    "FROM A a " +
    "JOIN B b ON b.idA = a.id " +
    "JOIN C c ON c.idA = a.id " +
    "WHERE a.title = 'title'",
    nativeQuery=true
    )
    public EntityA findByTitle();

    i would like thies method to return the full graph(A's with its B's and C's), and not only A's, lazy loading B's and C's.

    • Petri May 17, 2016 @ 19:19

      Hi,

      I have to admit that I don't know if JPA supports this. However, it is possible to do this if you are using Hibernate. Unfortunately you have to use Hibernate specific API. If you want to do this, you should check out this blog post.

  • Branko May 18, 2016 @ 17:30

    Hello,

    I am having problems getting Page results for generic repository.

    
    // find all by username - pageable
    Page findByUsername(final String username,Pageable pageable);  returns empty.
    
    

    also

    
    @Query("select d from #{#entityName} d where d.username= :username")
    Page findByUsername(@Param("username")final String username,Pageable pageable);
    
    

    Interesting thing is if i use List everything works just fine.
    Do you have any idea how to get around this issue?

    Thanks.

    • Branko May 19, 2016 @ 11:46

      Let me correct myself. It is not generic repository, rather another interface with custom methods.

      public interface MyRepository extends jpaRepository,IRepositoryWithUsername

      then

      
      public interface IRepositoryWithUsername {
      
      	@Query("select count (*) from #{#entityName} a where a.username=:username")
      	Long countForUsername(@Param("username")final String username);
      	
      	// find one
      	T2 findByIdAndUsername(final Long id,final String username);
      	
      	// find all by username - pageable
      	Page findByUsername(final String username,Pageable pageable);
      	
      	// find all by username 
      	List findByUsername(final String username);
      
      	// find all by username Sort
      	Iterable findAllByUsername(final String username,Sort sort);
      
      	T2 findOneByUsername(final String username);
      }
      
      

      Works fine for one instance and also List but Slice and Page are returning empty result.

      • Petri May 19, 2016 @ 19:31

        Hi,

        Hi have a vague memory that I had a similar problem (although I used the @Query annotation). If I remember correctly, I solved it by specifying the count query. You can specify it by annotating your query method with the @Query annotation and setting the count query as the value of the @Query annotation's countQuery attribute.

        Let me know if this solved your problem.

  • Farooq Ahmed May 27, 2016 @ 16:39

    Hi Dear,

    Your link has helped me a lot but I have a question.
    I want to write the native query in @Query annotation with where clause customized like select a.name from tableA a where [param1]=?1 and [param1]=?2

    If i do not send a param1 value it should not restrict the query upon it.

    • Petri May 27, 2016 @ 18:59

      Hi,

      Unfortunately you cannot use that query with the @Query annotation. You have to use either JPA criteria API or Querydsl.

      • Farooq Ahmed Rahu May 31, 2016 @ 14:48

        Hi Dear,

        I am using StringBuilder to use with createNamedQuery but on line javax.persistence.Query query = em.createNamedQuery(queryBuilder.toString()); it returns null pointer.

        do not know what is causing the exception.

        here is my code.

        
        @Repository
        @Transactional
        public class JpaUserDaoImpl implements UserDao 
        {
        	@PersistenceContext
        	private EntityManager em;
        	
        	@Override
        	@Transactional(readOnly=true)
        	public List getCustomerReport(String custId, String brandId) {
        		
        		 StringBuilder queryBuilder = new StringBuilder(
        			        "SELECT a FROM tableA c WHERE ");
        			    List paramList = new ArrayList();
        			    if(custId!=null && !custId.isEmpty()){
        			    paramList.add(" c.customer_id = '?'"
        			        .replace("?", custId));
        			    }
        			    if(brandId!=null && !brandId.isEmpty()){
        			    paramList.add(" o.brand_id = '?'"
        			        .replace("?", brandId));
        			    }			 
        			    Iterator itr = paramList.iterator();
        			    while(itr.hasNext()) {
        			        queryBuilder.append(itr.next());			       
        			        if (itr.hasNext()) {
        			            queryBuilder.append(" and ");
        			        }
        			    }
        ----> NPE       javax.persistence.Query query = em
        						.createNamedQuery(queryBuilder.toString()); 
        
        			    List resultList = (List)query.getResultList();
        			    return resultList;
        	}
        }
        
        

        Project based on Spring boot Spring JPA repositories.

        Thanks

        • Petri Jun 1, 2016 @ 21:13

          I cannot find any obvious bugs from your code. If the NullPointerException is thrown from that line, either EntityManager or StringBuilder is null. I recommend that you use a debugger since it will tell you which object is null.

          Also, since you are writing a dynamic query, you should use JPA Criteria API or Querydsl. These tools will make your code cleaner (if you use Querydsl) and safer.

  • Farooq Ahmed Rahu Jun 2, 2016 @ 8:23

    Dear Petri,
    I have just implemented the JDBC through which run queries and returning the list.
    See the chunk of code.
    [
    Connection con = getConnection();
    ResultSet resultset = getStatement(con).executeQuery(queryBuilder.toString());

    CustomerReportBean customerReportBean = new CustomerReportBean();
    while (resultset.next()) {
    customerReportBean = new CustomerReportBean();
    if (resultset.getString(1) != null) {
    customerReportBean.setDate(resultset.getString(1));
    customerReportList.add(customerReportBean);
    }
    // Closed connection
    con.close();
    ]

    private Connection getConnection() {
    DataSource ds = (DataSource) ApplicationContextProvider.getApplicationContext().getBean("dataSource");
    Connection con = null;
    try {
    con = ds.getConnection();
    } catch (SQLException e) {
    e.printStackTrace();
    }
    return con;
    }

    Kindly let me know is it a good practice or not, will it reduce the efficiency of the application.
    Just i am doing for reporting purpose as i am not using any reporting tool.

    Thanks

    • Petri Jun 2, 2016 @ 17:39

      Hi,

      Spring Framework provides an excellent support for invoking SQL queries. If you use the JdbcTemplate (or NamedParameterJdbcTemplate) you don't have to write so much boilerplate code. This keeps your code a lot cleaner. In other words, use the components provided by Spring Framework (you won't be disappointed).

      • Farooq Ahmed Rahu Jun 3, 2016 @ 8:59

        ✢ ✥ ✦ ✧ ❂ ❉ ✱ ✲ ✴ ✵ ✶ ✷ ✸ ❇ ✹ ✺ ✻ ✼ ❈ ✮ ✡
        Dear Petri,
        Thank you very much ...for helping...
        have implemented JdbcTemplate. now my code looks a lot cleaner .
        ✢ ✥ ✦ ✧ ❂ ❉ ✱ ✲ ✴ ✵ ✶ ✷ ✸ ❇ ✹ ✺ ✻ ✼ ❈ ✮ ✡

        • Petri Jun 4, 2016 @ 11:36

          You are welcome!

  • Anonymous Jan 19, 2018 @ 10:05

    Thank you sir your query is perfectly work in my project

    • Petri Jan 19, 2018 @ 17:07

      You are welcome!

  • Himanshu Jan 20, 2018 @ 12:37

    I want to write jpa query to select all record in date range with some fileters, I am using example for that but its not working. JPA query written by me is as follows

    public List findAllByOtActDateBetween(Example example,Date startDate,Date endDate);

    please help me to correct it..

    • Petri Jan 27, 2018 @ 9:53

      Hi,

      Unfortunately I need a bit more information before I can try to figure out what is wrong. Can I see the relevant entities? Also, when you say that your query is not working, what do you mean? Does it return too many rows, wrong rows, or no rows at all?

  • DevJok May 23, 2018 @ 21:19

    Hi, i have a question... if we have combined methos in an interface, some with @Query and some without @Query how we implements this interface, if we only would like override the method that has not @Query (I know that an implements has to defining all methods)?
    For example:

    /***********INTERFACE********/
    public interface UserRepository {
    @Query(value = "{ 'username' : ?0}")
    User findByUserName(String username);

    List findByFilter(Map map); //without @Query because needs mor complex implementation
    }
    /***********IMPLEMENTATION********/
    public class UserRepositoryImpl implements UserRepository{

    @Override //I don't want override this, because already exist the query.. but i have to define new the method?.. or i have use other interface for methods that not use @Query annotation?
    public User findByUserName(String username) {
    return null;
    }

    @Override //I only need override this method
    public User findByFilter(Map map) {
    return null;
    }

    }
    I hope you can help me.
    Awesome article!...

  • Aseem Jul 4, 2018 @ 15:28

    the response has already been committed. As a result, the response may have the wrong status code. Always getting this error...

    @Entity
    @NamedQuery( name = "Fields.findByName", query = "SELECT s FROM Fields s WHERE s.name = 'aseem'")
    @Table(schema = "ASEEM", name = "com.aseem.ph.db::Table.FIELDS_NEW")

    public interface FieldsDAO extends JpaRepository {
    public List findByName();
    }

    • Aseem Jul 6, 2018 @ 8:37

      Its solved, issue was in my return response..Thanks for the great article...

      • Petri Jul 9, 2018 @ 23:14

        Hi,

        I am happy to hear that you were able to solve your problem. Also, I am sorry that it took me so long before I answered to your comment. I am currently on summer holiday and my response times can be a bit slow.

  • Mohamoud Jul 9, 2018 @ 1:33

    how can we write Unit test for @Query annotation methods?

    • Petri Jul 9, 2018 @ 11:51

      Hi,

      You cannot write "pure" unit tests for these methods because they don't have an implementation when your unit tests are run. Also, you shouldn't write unit tests for data access code because unit tests won't help you to ensure that your data access code is working as expected. If you want to get more information about testing data access code, you should take a look at this tutorial.

      Now, because Spring Data JPA creates implementations for your query methods when Spring loads the application context of your application, you can test your query methods by writing integration tests. If you need more information about writing tests for Spring or Spring Boot web applications, you should take a look at my testing course.

  • srujan Jul 12, 2018 @ 0:27

    can i know how to add comment in @query annoation
    as we use /* my comment*/ because this comment did not work for me in jpa

    • Petri Jul 12, 2018 @ 10:34

      Hi,

      If you want to document your database query, you can document the repository method by using the Javadoc syntax.

      • Anonymous Jul 12, 2018 @ 16:48

        Actually i'm not rying to document it but i'm trying to identify the query using a label
        SELECT /*+label(myselectquery)*/ COUNT(*) FROM t;
        so if i use this label in my java jpa code
        my database team can easily identify the query where it belongs to and can help in better performance

        • Petri Jul 13, 2018 @ 11:14

          Hi,

          That makes perfect sense. Unfortunately it seems that the @Query annotation (or JPQL) doesn't support labels. Actually, it seems that the only way to add comments to the generated SQL is to use the Hibernate Criteria API.

  • AraM Aug 2, 2018 @ 5:34

    Hi. Thanks for the informative article. How can i pass a null to date column which is nullable. I couldn’t find anything like setNull in Prepared statements. Thank you.

    • Petri Aug 2, 2018 @ 10:13

      Hi,

      If you are using JPA (either Spring Data JPA or EntityManager), you can simply set the value of the entity's field to null and persist (or update) the entity.

  • Vinay Dec 3, 2018 @ 18:17

    Thanks for nice blog, Can we have subquery with Query block??

    • Petri Jan 15, 2019 @ 21:30

      Yes. You can use a subquery as long as it's supported by JPQL or SQL.

  • Levi Ciccarelli Jan 10, 2019 @ 21:35

    love the content

    • Petri Jan 15, 2019 @ 21:26

      Thank you for your kind words. I really appreciate them.

  • Deepak Jan 23, 2019 @ 12:07

    can we send a complete query string from my controller and execute it

    • Petri Jan 25, 2019 @ 22:40

      No. You cannot do this with Spring Data JPA.

  • Buminda Apr 24, 2019 @ 15:20

    Thanks for sharing...good one

  • babu Feb 27, 2020 @ 12:23

    multiple select queries at a time executing how to avoid multiple queries

  • Nike Jul 23, 2020 @ 22:23

    I want a code i,e. (if i want print only first it should display only with first name by using query annotation)
    I done with code but im having some errrors

    • Anonymous May 26, 2022 @ 12:16

      How to implement this interface in my service?

  • Aaron Jul 3, 2023 @ 21:04

    Hi, How to implement the Query Builder Tool with Search Filter using spring boot and jsp

    • Petri Jul 4, 2023 @ 9:01

      Hi,

      Before I can offer you any advice, I need to know the requirements of this query builder tool. Could you add them on this thread?

      • Aaron Jul 4, 2023 @ 10:44

        do you know Jira Query Language(JQL)

        • Petri Jul 4, 2023 @ 15:46

          No. I try to stay as far away from Jira administration / customization as possible.

          • Aaron Jul 4, 2023 @ 16:38

            ok, Thanks

  • Aaron Jul 28, 2023 @ 17:27

    Hi Petri, In my local spring boot-microservices application all APIs are working but when I deploy the application to the dev server so the time application is working but on the dev server randomly 400 null
    exception is coming how to fix this exception

    • Petri Jul 29, 2023 @ 9:10

      Hi,

      When the exception is thrown? Also, can you include the full stack trace when you answer to my comment?

Leave a Reply