Spring Boot Data JPA @Query
last modified July 25, 2023
In this article we show how to create custom queries with Data JPA @Query.
Spring is a popular Java application framework for creating enterprise applications. Spring Boot is an evolution of Spring framework which helps create stand-alone, production-grade Spring based applications with minimal effort.
Spring Data JPA
Spring Data JPA facilitates the implementation of JPA based repositories. It enhances support for JPA based data access layers. It makes it easier to build Spring-powered applications that use data access technologies. Spring Data JPA is a part of the larger Spring Data family.
Spring Data JPA @Query
The @Query
annotation declares finder queries directly on
repository methods. While similar @NamedQuery
is used on domain
classes, Spring Data JPA @Query annotation is used on Repository interface. This
frees the domain classes from persistence specific information, which is a good
thing.
Spring Boot Data JPA @Query example
The following application is a simple Spring Boot web application, which uses Data JPA @Query to create a custom query.
build.gradle ... src ├───main │ ├───java │ │ └───com │ │ └───zetcode │ │ │ Application.java │ │ ├───controller │ │ │ MyController.java │ │ ├───model │ │ │ City.java │ │ ├───repository │ │ │ CityRepository.java │ │ └───service │ │ CityService.java │ │ ICityService.java │ └───resources │ │ application.properties │ │ data-h2.sql │ │ schema-h2.sql │ ├───static │ │ index.html │ └───templates │ showCities.ftlh └───test └───java
This is the project structure.
plugins { id 'org.springframework.boot' version '3.1.1' id 'io.spring.dependency-management' version '1.1.0' id 'java' } group = 'com.zetcode' version = '0.0.1-SNAPSHOT' sourceCompatibility = '17' repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-freemarker' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'com.h2database:h2' }
The Gradle build file contains dependencies for H2 database, Freemarker template engine, and Spring Boot Data JPA.
spring.main.banner-mode=off spring.sql.init.platform=h2 logging.level.org.hibernate.SQL=DEBUG spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=none
In the application.properties
file we write various configuration
settings of a Spring Boot application. With the
spring.main.banner-mode
property we turn off the Spring banner.
The spring.sql.init.platform
sets the vendor name of the database.
It is used in the initialization scripts. The spring.jpa.show-sql
enables logging of SQL statements. Finally, the
spring.jpa.hibernate.ddl-auto
disables the automatic creation of
schemas from entities.
package com.zetcode.model; import java.util.Objects; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; @Entity @Table(name = "cities") public class City { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; private int population; public City() { } public City(String name, int population) { this.name = name; this.population = population; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getPopulation() { return population; } public void setPopulation(int population) { this.population = population; } @Override public int hashCode() { int hash = 7; hash = 79 * hash + Objects.hashCode(this.id); hash = 79 * hash + Objects.hashCode(this.name); hash = 79 * hash + this.population; return hash; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final City other = (City) obj; if (this.population != other.population) { return false; } if (!Objects.equals(this.name, other.name)) { return false; } return Objects.equals(this.id, other.id); } @Override public String toString() { var builder = new StringBuilder(); builder.append("City{id=").append(id).append(", name=") .append(name).append(", population=") .append(population).append("}"); return builder.toString(); } }
This is the City
entity. Each entity must have at least two
annotations defined: @Entity
and @Id
.
@Entity @Table(name = "cities") public class City {
The @Entity
annotation specifies that the class is an
entity and is mapped to a database table. The @Table
annotation
specifies the name of the database table to be used for mapping.
@Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id;
The @Id
annotation specifies the primary key of an entity and
the @GeneratedValue
provides for the specification of generation
strategies for the values of primary keys.
CREATE TABLE cities(id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255), population INT);
When the application is started, the schema-h2.sql
script
is executed. It creates a new database table.
INSERT INTO cities(name, population) VALUES('Bratislava', 432000); INSERT INTO cities(name, population) VALUES('Budapest', 1759000); INSERT INTO cities(name, population) VALUES('Prague', 1280000); INSERT INTO cities(name, population) VALUES('Warsaw', 1748000); INSERT INTO cities(name, population) VALUES('Los Angeles', 3971000); INSERT INTO cities(name, population) VALUES('New York', 8550000); INSERT INTO cities(name, population) VALUES('Edinburgh', 464000); INSERT INTO cities(name, population) VALUES('Suzhou', 4327066); INSERT INTO cities(name, population) VALUES('Zhengzhou', 4122087); INSERT INTO cities(name, population) VALUES('Berlin', 3671000);
Later, the data-h2.sql
file is executed. It fills the
table with data.
package com.zetcode.repository; import com.zetcode.model.City; import java.util.List; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; @Repository public interface CityRepository extends CrudRepository<City, Long> { @Query("select c from City c where c.name like %?1") List<City> findByNameEndsWith(String chars); }
By extending from the Spring CrudRepository
, we will have
some methods for our data repository implemented. In addition, we create a custom
findByNameEndsWith
method.
@Query("select c from City c where c.name like %?1") List<City> findByNameEndsWith(String chars);
The @Query
annotation contains the custom JPQL querty. It returns
cities whose names end in the provided characters.
package com.zetcode.service; import com.zetcode.model.City; import java.util.List; public interface ICityService { List<City> findByNameEndsWith(String name); }
ICityService
provides a contract method to get all cities ending in
certain characters.
package com.zetcode.service; import com.zetcode.model.City; import com.zetcode.repository.CityRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class CityService implements ICityService { private final CityRepository repository; @Autowired public CityService(CityRepository repository) { this.repository = repository; } @Override public List<City> findByNameEndsWith(String name) { return repository.findByNameEndsWith(name); } }
CityService
contains the implementation of the
findByNameEndsWith
method. We use the repository to retrieve data
from the database.
private final CityRepository repository; @Autowired public CityService(CityRepository repository) { this.repository = repository; }
CityRepository
is injected.
return repository.findByNameEndsWith(name);
The findByNameEndsWith
method of the repository returns the list of
cities ending in certain characters.
package com.zetcode.controller; import com.zetcode.model.City; import com.zetcode.service.ICityService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import java.util.List; @Controller public class MyController { private final ICityService cityService; @Autowired public MyController(ICityService cityService) { this.cityService = cityService; } @GetMapping(path="showCitiesEnding") public String findCitiesNameEndsWith(Model model, @RequestParam String name) { var cities = (List<City>) cityService.findByNameEndsWith(name); model.addAttribute("cities", cities); return "showCities"; } }
The MyController
class is annotated with @Controller
.
private final ICityService cityService; @Autowired public MyController(ICityService cityService) { this.cityService = cityService; }
We inject a ICityService
into the cityService
field.
@GetMapping("/showCitiesEnding") public String findCitiesNameEndsWith(Model model, @RequestParam String name) { var cities = (List<City>) cityService.findByNameEndsWith(name); model.addAttribute("cities", cities); return "showCities"; }
We map a request with the showCitiesEnding
path to the controller's
findCitiesNameEndsWith
method. The model gains a list of matching
cities and the processing is sent to the showCities.ftl
Freemarker
template file.
<!DOCTYPE html> <html> <head> <title>Cities</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> </head> <body> <h2>List of cities</h2> <table> <tr> <th>Id</th> <th>Name</th> <th>Population</th> </tr> <#list cities as city> <tr> <td>${city.id}</td> <td>${city.name}</td> <td>${city.population}</td> </tr> </#list> </table> </body> </html>
In the showCities.ftlh
template file, we display the data in
an HTML table.
<!DOCTYPE html> <html> <head> <title>Home page</title> <meta charset="UTF-8"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> </head> <body> <a href="showCitiesEnding?name=ou">Show cities ending in ou</a> </body> </html>
In the index.html
there is a link to show cities ending in ou characters.
package com.zetcode; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
The Application
sets up the Spring Boot application. The
@SpringBootApplication
enables auto-configuration and component
scanning.
$ ./gradlew bootRun
After the application is run, we can navigate to localhost:8080
.
In this article we have showed how to use Spring Data JPA @Query annotation to create a custom JPQL query.