In this post we will talk about different ways to write custom query in Spring Data JPA.

Spring JPA Overview

We know Spring Data JPA brings CRUD implementation out of the box and along with Spring JPA provides you some easy method to write the query based on method name. It provides some Generic Crud operations like save(), findById(), deleteById(), findAll() and so on. There are many such method which comes out of the box and can use without even implementing.

Some simple use-cases can be written with derived query methods by simply defining the method name of the entity class inside the repository interface. However, in some of the case these queries might not be enough and we might need to write custom query.

In this post, we will look into how to use @Query annotation to execute custom queries for both SQL and JPQL queries.

@Query annotation

The method is annotated with @Query annotation and the queries is passed as a value to be executed. @Query annotation gives full flexibility to execute query as required without relying of naming convention of method. 

JPQL

Also know as Java Persistence query language. It is more preferred as it allows you to define query based on JPA entity and it is database agnostic. The query is parse by Spring Data JPA before it submits it to database. There is some drawback for using JPQL query. It only supports a subset of SQL standard and not a fit for complex query.

public interface PostRepository extends JpaRepository<Post, Integer> {
 
    @Query("FROM Post WHERE name = ?1")
    List<Post> findByName(String name);
 
    @Query("SELECT a FROM Post a WHERE Name = ?1 AND Author = ?2")
    List<Author> findByNameAndAuthor(String name, String author);
 
}

With JPQL query you can write query treating table columns as attributes of the class. So, basically the query seems like it is executing on the Java persistent class.

Sorting by using JPQL Query 

There are two ways to achieve this. One is by using ORDER BY Clause and the next by Sort in the method argument. With Sort object in the method, hibernate will add ORDER BY clause in the query.

public interface PostRepository extends JpaRepository<Post, Integer> {
         
    @Query("FROM Post WHERE Name = ?1 ORDER BY Author ASC")
    List<Post> findByNameOrderByAuthor(String name);
}

Native Queries

We can write native queries inside @Query annotation as well. The query is executed by Hibernate or EclipseLink as for JPQL.The query is kind of similar to JPQL queries, however, we should tell Spring JPA that we want the query to be executed as native query and that is done by setting the nativeQuery property as true.

The native query is not parsed by Spring JPA but is rather sent to database directly. So, the query is written as if the query is executed directly on database.

    @Query(value = "SELECT * FROM post", nativeQuery = true)
    List<Post> findAllPost();

    @Query(value = "SELECT * FROM post WHERE name = :name", nativeQuery = true)
    List<Post> findCustomPostsByName(@Param("name") String name);

Spring Data JPA replaces the value of each positional parameter with the value passed as argument in the methods. This is Named Parameters representation of queries. Named parameter starts with :name and the value is defined inside method argument

Setup Spring Boot JPA Project

Let’s use Spring Starter tool to generate a project with one click. You can add the dependencies we need while generating the project: Spring Web, Spring Data JPA.

Once you download the project, your pom should look like this:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.6.7</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.fullstack.coder</groupId>
	<artifactId>custom-query</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>custom-query</name>
	<description>Demo project for Spring Boot Custome Query</description>
	<properties>
		<java.version>11</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

        <dependency>
	        <groupId>mysql</groupId>
	        <artifactId>mysql-connector-java</artifactId>
	        <scope>runtime</scope>
        </dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

Configure Spring Boot JPA

We need one more dependency for datasource. As i have mentioned above, you can use any datasource you like and for this part we will be using SQL as data source.

Configure SQL

long with it, we also need to update application.properties file.

spring.datasource.url= jdbc:mysql://localhost:3306/your_database_name?useSSL=false
spring.datasource.username= your_username
spring.datasource.password= your_password
spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto= update

  • spring.datasource.url is used to define database url. “jdbc:h2:mem:[your_database_name]” is how you define the url for H2
  • spring.datasource.username and spring.datasource.password are the credentials to connect to database.
  • spring.jpa.hibernate.ddl-auto is used to initialize database. It will create database tables automatically based on our defined model. You can also set this property to following values: none, validate, update, create-drop. For production, it is better to use validate as property.
  • spring.h2.console.enabled=true will tell Spring to start H2 Database administration tool. We can access this on http://localhost:8080/h2-console
  • spring.jpa.show-sql=true is to log database transaction on the console.
  • spring.jpa.properties.hibernate.dialect is to configure Hibernate for JPA implementation. It’s different for MySQL and PostgreSQL.
  • Define Data Model class for Spring JPA

    Since our main objective is to understand Custom Query, we will be defining Post class inside package named model.

    Post

    package com.fullstack.coder.custom.query.model;
    
    import javax.persistence.*;
    
    @Entity
    @Table(name = "post")
    public class Post {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Integer id;
        @Column(name = "name")
        private String name;
        @Column(name = "description")
        private String description;
        @Column(name = "author")
        private String author;
    
    
        public Post() {
        }
    
        public Post(String name, String description, String author) {
            this.name = name;
            this.description = description;
            this.author = author;
        }
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getDescription() {
            return description;
        }
    
        public void setDescription(String description) {
            this.description = description;
        }
    
        public String getAuthor() {
            return author;
        }
    
        public void setAuthor(String author) {
            this.author = author;
        }
    
        public String toString(){//overriding the toString() method
            return id+" "+name+" "+description+ " "+ author;
        }
    }
    
    

    @Entity

    This annotation tells Spring JPA that the class is a persistent Java class and the table has to be mapped on to the database.

    @Column

    Table is supposed to have different columns and this column is mapped to attributes name in Java persistent class. @Column annotation tells Spring JPA that the attribute is a column of the associated table. With name property column name can be given too.

    @Table

    The annotation maps the class name to a table name in datasource. “name” property defines table name.

    @Id

    A table needs to have a unique key as an attribute based on which you can perform query for uniqueness. @Id annotation tells that the column is a primary key. The primary key can be generated by different ways. @GeneratedValue defines how the primary key will be generated.

    Create Repository Interface for Spring JPA Custom Query

    Inside the repository package, let’s create one interface that extends JpaRepository; PostRepository inside repository package.

    PostRepository

    package com.fullstack.coder.custom.query.repository;
    
    
    import com.fullstack.coder.custom.query.model.Post;
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.data.jpa.repository.Query;
    import org.springframework.data.repository.query.Param;
    
    import java.util.List;
    
    public interface PostRepository extends JpaRepository<Post, Integer> {
        @Query("FROM Post WHERE name = ?1")
        List<Post> findByPostName(String name);
    
        @Query("SELECT a FROM Post a WHERE name = ?1 AND author = ?2")
        List<Post> findByPostNameAndAuthor(String name, String author);
    
        @Query("FROM Post WHERE name = ?1 ORDER BY name ASC")
        List<Post> findByPostNameOrderByName(String name);
    
        @Query("SELECT p FROM Post p WHERE p.name LIKE %?1%")
        List<Post> findByNameLike(String name);
    
        @Query(value = "SELECT * FROM post", nativeQuery = true)
        List<Post> findAllPost();
    
        @Query(value = "SELECT * FROM post WHERE name = :name", nativeQuery = true)
        List<Post> findCustomPostsByName(@Param("name") String name);
    
    }
    
    

    Define Rest Controller for Custom Query

    package com.fullstack.coder.custom.query.controller;
    
    
    import com.fullstack.coder.custom.query.model.Post;
    import com.fullstack.coder.custom.query.repository.PostRepository;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.*;
    
    import java.util.Optional;
    
    
    @RestController
    public class PostController {
    
        @Autowired
        PostRepository postRepository;
    
    
        @GetMapping("/post/{id}")
        public ResponseEntity<Post> getPostById(@PathVariable("id") int id) {
            Optional<Post> post = postRepository.findById(id);
            if(post.isEmpty()) {
                return new ResponseEntity<>(HttpStatus.NOT_FOUND);
            }
            return new ResponseEntity<>(post.get(), HttpStatus.OK);
        }
    
        @PostMapping("/post")
        public ResponseEntity<Post> createPost(@RequestBody Post post) {
            Post resPost = postRepository.save(new Post(post.getName(), post.getDescription(), post.getAuthor()));
            return new ResponseEntity<>(resPost, HttpStatus.CREATED);
        }
    
        @GetMapping("/post/derived")
        public ResponseEntity<HttpStatus> getCustomQueryPost() {
    
            postRepository.findByPostName("Post1").
                    forEach(p -> System.out.println(p.toString()));
            System.out.println();
    
            postRepository.findByPostNameAndAuthor("Post1","author1")
                    .forEach(p -> System.out.println(p.toString()));
            System.out.println();
    
            postRepository.findByPostNameOrderByName("Post1").
                    forEach(p -> System.out.println(p.toString()));
            System.out.println();
    
            postRepository.findCustomPostsByName("Post1").
                    forEach(p -> System.out.println(p.toString()));
            System.out.println();
    
            postRepository.findAllPost().
                    forEach(p -> System.out.println(p.toString()));
            System.out.println();
    
            postRepository.findByNameLike("Post1").
                    forEach(p -> System.out.println(p.toString()));
    
            return new ResponseEntity<>(HttpStatus.OK);
        }
    
        @DeleteMapping("/post/{id}")
        public ResponseEntity<HttpStatus> deletePostById(@PathVariable("id") int id) {
            postRepository.deleteById(id);
            return new ResponseEntity(HttpStatus.NO_CONTENT);
        }
    
    }
    
    

    Conclusion

    Spring JPA is amazing as it provides many out of box functionalities. In this post we saw how we can write our own custom query to connect to database instead of out of box queries.