In this post we will talk about different ways to write derived 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.
Structure of Derived Query Methods in Spring Data JPA
Derived Query method name has mainly two parts. The first part known as introducer and the second as criteria.
Introducer: Spring JPA supports clause like find, query, read, count and get based on which Spring Data JPA knows what action to take. There are certain expression that too can be used like Distinct to truncate our result set.
Criteria: Spring JPA supports clause like By keyword and is followed by the entity attributes to query on. Different conditions like AND, OR, IS, ISNOT, ISNULL and many others can also be applied.
You just need to follow conventions while writing derived methods. As long as your query method name starts with find…By, read…By, query…By, count…By, or get…By and follows the right criteria pattern, Spring Data translates it to create the required JPQL query.
Some of the examples are as follows:
# find posts by name
List<Post> findByName(String name);
# find posts by name or author
List<Post> findByNameOrAuthor(String name, String author);
# find posts by name and author
List<Post> findByNameAndAuthor(String name, String author);
Name or Author in the above example is attribute of the Java persistent class.
Using OR or AND use case in derived query
List<Post> findByNameOrAuthor(String name, String author);
List<Post> findByNameAndAuthor(String name, String author);
OR/AND allows to perform query with multiple arguments or attributes.
Null check use case in derived query
List<Post> findByNameIsNull();
List<Post> findByNameIsNotNull();
This query checks for null as value in name attribute. Similarly NotNull will retrieve result which are not null.
Is, IsNot, Equals and Not use case in derived query
These are conditional query to check for the exact name or not. By the way Is and Equals has the exact result.
List<Post> findByNameIs(String name);
List<Post> findByNameEquals(String name);
List<Post> findByNameIsNot(String name);
List<Post> findByNameNot(String name);
OrderBy, StartsWith, Containing use case in derived query
List<Post> findByNameStartingWith(String prefix);
List<Post> findByNameStartsWith(String prefix);
List<Post> findByNameContaining(String infix);
List<Post> findByNameOrderByAuthor(String name);
List<Post> findByNameOrderByAuthorAsc(String name);
The ORDER BY keyword is used to sort the result-set in ascending or descending order.
Setup Spring Boot JPA Project
Let’s use Spring Starter tool to generate a project with one click. You can add the three dependency 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.rtech</groupId>
<artifactId>derivedquery</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>derivedquery</name>
<description>Demo project for Spring Data JPA Derived 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>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 need to add one more dependency depending on the datasource.
Configure H2
To add H2 dependency in pom.xml.
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
long with it, we also need to update application.properties file.
spring.datasource.url=jdbc:h2:mem:your_database_name //i will be using jpadb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto= update
spring.h2.console.enabled=true
# h2-console is default path for H2
Define Data Model class for Spring JPA
Since our main objective is to understand Derived query, we will be defining Post class inside package named model.
Post
package com.rtech.derivedquery.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 Derived Query
Inside the repository package, let’s create one interface that extends JpaRepository; PostRepository inside repository package.
PostRepository
package com.rtech.derivedquery.repository;
import com.rtech.derivedquery.model.Post;
import org.springframework.data.jpa.repository.JpaRepository;
import javax.transaction.Transactional;
import java.util.List;
public interface PostRepository extends JpaRepository<Post, Integer> {
List<Post> findByName(String name);
List<Post> findDistinctByName(String name);
List<Post> findByNameOrAuthor(String name, String author);
List<Post> findByNameAndAuthor(String name, String author);
List<Post> findByNameIs(String name);
// OR
List<Post> findByNameEquals(String name);
List<Post> findByNameIsNot(String name);
// OR
List<Post> findByNameNot(String name);
List<Post> findByNameIsNull();
List<Post> findByNameIsNotNull();
List<Post> findByNameStartingWith(String prefix);
List<Post> findByNameStartsWith(String prefix);
List<Post> findByNameContaining(String infix);
List<Post> findByNameOrderByAuthor(String name);
List<Post> findByNameOrderByAuthorAsc(String name);
List<Post> findByNameOrderByAuthorDesc(String name);
@Transactional
void deleteByName(String name);
}
Define Rest Controller for Derived Query
package com.rtech.derivedquery.controller;
import com.rtech.derivedquery.model.Post;
import com.rtech.derivedquery.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> getDerivedPost() {
postRepository.findByName("Post1").forEach(p->System.out.println(p.toString()));
postRepository.findByNameIs("Post1").forEach(p->System.out.println(p.toString()));
postRepository.findByNameEquals("Post1").forEach(p->System.out.println(p.toString()));
postRepository.findByNameIsNot("Post1").forEach(p->System.out.println(p.toString()));
postRepository.findByNameNot("Post1").forEach(p->System.out.println(p.toString()));
postRepository.findByNameOrAuthor("","author1").forEach(p->System.out.println(p.toString()));
postRepository.findByNameAndAuthor("Post","author1").forEach(p->System.out.println(p.toString()));
postRepository.findByNameIsNull().forEach(p->System.out.println(p.toString()));
postRepository.findByNameIsNotNull().forEach(p->System.out.println(p.toString()));
postRepository.findByNameStartingWith("P").forEach(p->System.out.println(p.toString()));
postRepository.findByNameStartsWith("Po").forEach(p->System.out.println(p.toString()));
postRepository.findByNameContaining("s").forEach(p->System.out.println(p.toString()));
postRepository.findByNameOrderByAuthor("Post1").forEach(p->System.out.println(p.toString()));
postRepository.deleteByName("Post1");
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 simply use attribute name of class to generate a query method.