The Named Query in JPA provides a feature to specify JPQL queries directly in the JPA Entity class and execute them by name. This is a @NamedQuery annotation used in JPA Entity class. This annotation is used when executing a JPQL query by name. We can use @NamedQuery in JPA by adding it with its attributes(name and query) in the Entity class. A name attribute is used to assign the unique name for the JPQL query and the query attribute specifies the JPQL query. In this topic, we will demonstrate to use of @NamedQuery in JPA using the Spring Boot application.
What is Named Query in JPA?
The Named Query(@NamedQuery) is an annotation used to define a JPQL query with a unique name.
Features of @NamedQuery
- It allows writing JPQL queries.
- It executes by the name.
- It provides reusability by defining JPQL queries once and executing them multiple times where we need them.
Syntax of @NamedQuery
Here’s the general syntax for @NamedQuery annotation:
@NamedQuery(
name = "queryName",
query = "JPQL query here"
)
- name: A unique identifier for the query.
- query: The JPQL query string.
Use @NamedQuery in JPA
There are the following steps to use @NamedQuery in JPA:
1. Create a JPA Entity
Create a Java Bean class annotated with @Entity annotation to make that class a JPA Entity class.
Example
@Entity
@Data
public class Orders {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String product;
private double price;
private String status;
}
2. Use @NamedQuery annotation
Use this annotation in the created entity class Orders.
Example
@Entity
@Data
@NamedQuery(
name = "findOrdersByStatus",
query = "SELECT o FROM Orders o WHERE status = :status"
)
public class Orders {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String product;
private double price;
private String status;
}
- In the above, we used @NamedQuery annotation to fetch orders based on a custom JPQL query.
- name = “findOrdersByStatus”: is the unique name for the query.
- query = “SELECT o FROM Orders o WHERE status = :status”: The JPQL query with a parameterized.
3. Executing @NamedQuery in Code
We can execute @NamedQuery’s JPQL query using the EntityManager.
Example
@Service
public class OrderServiceImpl implements OrderService {
@PersistenceContext
private EntityManager entityManager;
@Override
public List<Orders> getOrdersByStatus(String status) {
Query query = entityManager.createNamedQuery("findOrdersByStatus");
query.setParameter("status", status);
return query.getResultList();
}
}
- The @PersistenceContext annotation takes care of creating a unique EntityManager for every transaction (communication with the database).
- createNamedQuery: Executes the query by its name.
- setParameter(“status”, status): Sets the value for the parameter in the query.
- getResultList(): Returns the list of results mapped to the entity.
We can also execute the @NamedQuery query using JPA Repository.
Example
public interface OrderRepository extends JpaRepository<Orders, Long> {
@Query(name = "findOrdersByStatus")
List<Orders> findOrdersByStatus(String status);
}
- @Query(name = “findOrdersByStatus”): Links the repository method to the Named Query.
- (String status): Binds the method parameter to the query parameter.
Use @NamedQuery with other conditions
We can use @NameQuery with other conditions to make a JPQL query in the Entity class.
Example
@NamedQuery(
name = "findOrdersByPriceRange",
query = "SELECT o FROM Orders o WHERE o.price BETWEEN :minPrice AND :maxPrice"
)
Execution of @NamedQuery
public List<Orders> getOrdersByPriceRange(double minPrice, double maxPrice) {
Query query = entityManager.createNamedQuery("findOrdersByPriceRange");
query.setParameter("minPrice", minPrice);
query.setParameter("maxPrice", maxPrice);
return query.getResultList();
}
Using @NamedQuery for mapping DTO
We can map the results of @NamedQuery to a custom DTO instead of an entity.
Example: Mapping DTO
@Data
@AllArgsConstructor
public class OrderDTO {
private String product;
private double price;
}
Orders Entity with Result DTO Mapping
@Data
@Entity
@NamedQuery(
name = "findOrderDTOsByStatus",
query = "SELECT NEW com.springjava.dto.OrderDTO(o.product, o.price) FROM Orders o WHERE o.status = :status"
)
public class Orders {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String product;
private double price;
private String status;
}
- We used JPQL with constructor expression to return DTO using @NamedQuery.
Execution of @NamedQuery to Fetch DTO
public List<OrderDTO> getOrderDTOsByStatus(String status) {
Query query = entityManager.createNamedQuery("findOrderDTOsByStatus");
query.setParameter("status", status);
return query.getResultList();
}
Step-by-step to Guide the Use @NamedQuery in JPA
Let’s create a Spring Boot Application step-by-step guide using @NamedQuery annotation to define JPQL query in JPA Entity to fetch records from the database table. We will create an example that returns a list of orders to the client through the API endpoints using the mentioned ways above.
These are the following steps:
- Create Spring Boot Project
- Setup in the IDE
- Configure H2 Database
- Create a JPA Entity
- Create a Repository Interface
- Create a Service Interface
- Implement the Service Interface
- Create a Controller
- Run the Spring Boot Application
1. Create Spring Boot Project
We are creating a Spring Boot Project from the web tool Spring Initializr. By Providing details for the project and select the following Maven dependencies:
- Spring Web
- Spring Data JPA
- H2 Database
- Lombok
2. Setup in the IDE
We use Eclipse IDE to set up and configure the created Spring Boot Project. You can use other IDE to set up and configure the Spring Boot project.
Project Structure of Spring Boot
This image shows the project structure of Spring Boot in Eclipse IDE.
Maven Dependency
Here is the complete maven dependencies file pom.xml for the project which will implement the @NamedQuery in JPA using Spring Boot Application.
pom.xml
<?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>3.3.0</version>
<relativePath/>
<!-- lookup parent from repository -->
</parent>
<groupId>com.springjava</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</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>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</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>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
3. Configure H2 Database
We are going to configure the H2 database connection in the application.properties file.
application.properties
#H2 Database Configuration
spring.datasource.url=jdbc:h2:mem:test
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
4. Create a JPA Entity
Let’s create a JPA Entity class. For example, consider Orders entity and use Lombok for generating setter and getter methods, a constructor, etc.
Orders.java
package com.springjava.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.NamedQuery;
import lombok.Data;
@Data
@Entity
@NamedQuery(
name = "findOrdersByStatus",
query = "SELECT o FROM Orders o WHERE status = :status"
)
@NamedQuery(
name = "findOrdersByPriceRange",
query = "SELECT o FROM Orders o WHERE o.price BETWEEN :minPrice AND :maxPrice"
)
@NamedQuery(
name = "findOrderDTOsByStatus",
query = "SELECT NEW com.springjava.dto.OrderDTO(o.product, o.price) FROM Orders o WHERE o.status = :status"
)
public class Orders {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String product;
private double price;
private String status;
}
5. Create a Repository Interface
Create a repository interface for the Orders JPA Entity class. This interface extends the JpaRepository interface and performs persistence operations using the query method (saveAll) on the orders database table.
OrderRepository.java
package com.springjava.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import com.springjava.entity.Orders;
public interface OrderRepository extends JpaRepository<Orders, Long> {
@Query(name = "findOrdersByStatus")
List<Orders> findOrdersByStatus(String status);
}
6. Create a Service Interface
Create a Service interface OrderService with some method declaration.
OrderService.java
package com.springjava.service;
import java.util.List;
import com.springjava.dto.OrderDTO;
import com.springjava.entity.Orders;
public interface OrderService {
void saveAll(List<Orders>orderList);
List<Orders> getOrdersByStatus(String status);
List<Orders>getOrdersByPriceRange(double minPrice, double maxPrice);
List<OrderDTO> getOrderDTOsByStatus(String status);
}
7. Implement the Service Interface
Implementing the OrderService interface in the OrderServiceImpl class. This class is annotated with @Service annotation, where we inject OrderRepository to call all its methods.
OrderServiceImpl.java
package com.springjava.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.springjava.dto.OrderDTO;
import com.springjava.entity.Orders;
import com.springjava.repository.OrderRepository;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.Query;
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderRepository orderRepo;
@PersistenceContext
private EntityManager entityManager;
@Override
public void saveAll(List<Orders> orderList) {
orderRepo.saveAll(orderList);
}
@Override
public List<Orders> getOrdersByStatus(String status) {
return orderRepo.findOrdersByStatus(status);
}
@SuppressWarnings("unchecked")
@Override
public List<Orders> getOrdersByPriceRange(double minPrice, double maxPrice) {
Query query = entityManager.createNamedQuery("findOrdersByPriceRange");
query.setParameter("minPrice", minPrice);
query.setParameter("maxPrice", maxPrice);
return query.getResultList();
}
@SuppressWarnings("unchecked")
@Override
public List<OrderDTO> getOrderDTOsByStatus(String status) {
Query query = entityManager.createNamedQuery("findOrderDTOsByStatus");
query.setParameter("status", status);
return query.getResultList();
}
}
8. Create a Controller
Create a controller class OrderController. This is annotated with @RestController to make this class a Rest Controller.
OrderController.java
package com.springjava.controller;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.springjava.dto.OrderDTO;
import com.springjava.entity.Orders;
import com.springjava.service.OrderService;
@RestController
@RequestMapping("/api/order")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping("/save-all")
public ResponseEntity<?> save(@RequestBody List<Orders> orderList) {
Map<String, Object> respOrder = new LinkedHashMap<String, Object>();
orderService.saveAll(orderList);
respOrder.put("status", 1);
respOrder.put("message", "Record is Saved Successfully!");
return new ResponseEntity<>(respOrder, HttpStatus.CREATED);
}
@GetMapping("/orders-by-status")
public ResponseEntity<?> getOrdersByStatus() {
Map<String, Object> respOrder = new LinkedHashMap<String, Object>();
List<Orders> orderList= orderService.getOrdersByStatus("PENDING");
if (!orderList.isEmpty()) {
respOrder.put("status", 1);
respOrder.put("data", orderList);
return new ResponseEntity<>(respOrder, HttpStatus.OK);
} else {
respOrder.clear();
respOrder.put("status", 0);
respOrder.put("message", "Data is not found");
return new ResponseEntity<>(respOrder, HttpStatus.NOT_FOUND);
}
}
@GetMapping("/orders-by-price-range")
public ResponseEntity<?> getOrdersByPriceRange() {
Map<String, Object> respOrder = new LinkedHashMap<String, Object>();
List<Orders> orderList= orderService.getOrdersByPriceRange(1700, 2000);
if (!orderList.isEmpty()) {
respOrder.put("status", 1);
respOrder.put("data", orderList);
return new ResponseEntity<>(respOrder, HttpStatus.OK);
} else {
respOrder.clear();
respOrder.put("status", 0);
respOrder.put("message", "Data is not found");
return new ResponseEntity<>(respOrder, HttpStatus.NOT_FOUND);
}
}
@GetMapping("/orders-dto-by-status")
public ResponseEntity<?> getOrderDTOsByStatus() {
Map<String, Object> respOrder = new LinkedHashMap<String, Object>();
List<OrderDTO> orderList= orderService.getOrderDTOsByStatus("COMPLETED");
if (!orderList.isEmpty()) {
respOrder.put("status", 1);
respOrder.put("data", orderList);
return new ResponseEntity<>(respOrder, HttpStatus.OK);
} else {
respOrder.clear();
respOrder.put("status", 0);
respOrder.put("message", "Data is not found");
return new ResponseEntity<>(respOrder, HttpStatus.NOT_FOUND);
}
}
}
9. Run the Spring Boot Application
Right-click this Spring Boot application on the DemoApplication.java, then click Run As and select Java Application.
H2 Database Console
To check the H2 database console we need to browse the URL “http://localhost:[server_port]/h2-console” on the browser.
JSON Array
We are creating a sample JSON Array to test the API http://localhost:8080/api/order/save-all.
[
{
"product": "Laptop",
"price": 2000,
"status":"PENDING"
},
{
"product": "Mobile",
"price": 1800,
"status":"COMPLETED"
},
{
"product": "Mobile",
"price": 1600,
"status":"CANCELLED"
}
]
Test the APIs on the Postman Tool
POST: http://localhost:8080/api/order/save-all
GET: http://localhost:8080/api/order/orders-by-status
This API hits then Spring Data JPA (internally uses Hibernate as a JPA provider) generates SQL statement in the console below here:
Hibernate: select o1_0.id,o1_0.price,o1_0.product,o1_0.status from orders o1_0 where o1_0.status=?
GET:http://localhost:8080/api/order/orders-by-price-range
This API hits then Spring Data JPA (internally uses Hibernate as a JPA provider) generates SQL statement in the console below here:
Hibernate: select o1_0.id,o1_0.price,o1_0.product,o1_0.status from orders o1_0 where o1_0.price between ? and ?
GET: http://localhost:8080/api/order/orders-dto-by-status
This API hits then Spring Data JPA (internally uses Hibernate as a JPA provider) generates SQL statement in the console below here:
Hibernate: select o1_0.product,o1_0.price from orders o1_0 where o1_0.status=?
Best Practices for Using @NamedQuery
- It ensures query names clearly indicate their purpose (e.g., findByStatus).
- Use Named Queries for frequently executed queries to improve maintainability.
- Verify the correctness of Named Queries against your database schema.
- Always use parameters (:param) to prevent SQL injection and improve flexibility.
- For complex queries, ensure proper indexing and optimisation of columns in WHERE clauses.
Advantages of @NamedQuery
- It is defined once and reused multiple times.
- It improves code readability by separating query logic from application logic.
- It works seamlessly with JPA and Hibernate tooling for query validation.
Conclusion
Using @NamedQuery annotation in JPA with Spring Boot allows us to define and execute reusable, parameterized queries efficiently. Whether we are fetching entities or mapping results to custom DTOs using @NamedQuery offers a clean and maintainable solution for handling complex query requirements in our applications.