Create and Call Stored Procedure in Spring Boot with JPA
In this tutorial, you will learn how to create MySQL stored procedures
and call them from a Spring Boot application using JPA's
@NamedStoredProcedureQuery and EntityManager.
You will build two procedures — one to insert a student and one to fetch all students — then expose them through REST endpoints backed by Spring Data JPA. This approach keeps complex SQL logic inside the database while still using the familiar Spring Boot programming model.
Project Structure
The Spring Boot project follows a clean layered package structure:
Creating Stored Procedures in MySQL
First, create the database and both stored procedures directly in your MySQL client.
The DELIMITER is required because the procedure body contains semicolons.
mysql> create database students;
mysql> use students;
# Procedure 1: Insert a student
mysql> DELIMITER $$
mysql> create procedure addStudent(IN id int, IN name varchar(35), IN marks float)
-> BEGIN
-> insert into student values(id, marks, name);
-> END $$
Query OK, 0 rows affected (0.02 sec)
mysql> DELIMITER ;
# Procedure 2: Fetch all students
mysql> DELIMITER $$
mysql> create procedure fetchStudents()
-> BEGIN
-> select * from student;
-> END $$
Query OK, 0 rows affected (0.01 sec)
mysql> DELIMITER ;
Why DELIMITER is required: The MySQL client uses ; to end statements. Since the procedure body itself contains ;, we temporarily change the delimiter to $$ so the entire procedure is treated as one statement.
ProcedureTutorialApplication.java
Standard Spring Boot entry point.
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ProcedureTutorialApplication {
public static void main(String[] args) {
SpringApplication.run(ProcedureTutorialApplication.class, args);
}
}
application.properties
server.port=8081
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.url=jdbc:mysql://localhost:3306/students
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
Note: In modern Spring Boot (2.4+), the MySQL driver is auto-detected. If you must specify it, use com.mysql.cj.jdbc.Driver (the old com.mysql.jdbc.Driver is deprecated).
StudentController.java
Two REST endpoints are exposed — one to add a student (via the addStudent
procedure) and one to fetch all students (via fetchStudents).
package com.example.demo.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
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.RestController;
import com.example.demo.model.Student;
import com.example.demo.service.StudentService;
@RestController
public class StudentController {
@Autowired
private StudentService studentService;
@PostMapping("/addstudent")
public Integer addStudent(@RequestBody Student student) {
return studentService.addStudent(student);
}
@GetMapping("/students")
public List<Student> fetchStudents() {
return studentService.fetchStudents();
}
}
Student.java (Entity + @NamedStoredProcedureQuery)
The entity maps to the student table and declares both stored procedures
using @NamedStoredProcedureQueries. Each parameter is declared with
@StoredProcedureParameter and a ParameterMode.
package com.example.demo.model;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedStoredProcedureQueries;
import javax.persistence.NamedStoredProcedureQuery;
import javax.persistence.ParameterMode;
import javax.persistence.StoredProcedureParameter;
@Entity
@NamedStoredProcedureQueries({
@NamedStoredProcedureQuery(
name = "addStudent",
procedureName = "addStudent",
parameters = {
@StoredProcedureParameter(mode = ParameterMode.IN, name = "id", type = Integer.class),
@StoredProcedureParameter(mode = ParameterMode.IN, name = "name", type = String.class),
@StoredProcedureParameter(mode = ParameterMode.IN, name = "marks", type = Float.class)
}
),
@NamedStoredProcedureQuery(
name = "fetchStudents",
procedureName = "fetchStudents"
)
})
public class Student {
@Id
private int id;
private String name;
private float marks;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getMarks() {
return marks;
}
public void setMarks(float marks) {
this.marks = marks;
}
}
Key annotations:
@NamedStoredProcedureQueries— container for multiple procedures on the same entity.@NamedStoredProcedureQuery(name, procedureName)— maps a Java name to the DB procedure.@StoredProcedureParameter(mode, name, type)— declares each parameter. Must match the procedure's parameter names.ParameterMode.IN— input parameter (there are alsoOUTandINOUT).
StudentRepository.java
The repository extends JpaRepository. In this project, it is used mainly for
Hibernate's DDL generation; the actual procedure calls go through
EntityManager.
package com.example.demo.repo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.example.demo.model.Student;
@Repository
public interface StudentRepository extends JpaRepository<Student, Integer> {
}
StudentService.java
The service injects EntityManager and calls the named procedures. Both
methods are annotated with @Transactional — without it, JPA throws
TransactionRequiredException.
package com.example.demo.service;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.demo.model.Student;
@Service
public class StudentService {
@PersistenceContext
private EntityManager entityManager;
@Transactional
public int addStudent(Student student) {
return entityManager.createNamedStoredProcedureQuery("addStudent")
.setParameter("id", student.getId())
.setParameter("name", student.getName())
.setParameter("marks", student.getMarks())
.executeUpdate();
}
@Transactional
@SuppressWarnings("unchecked")
public List<Student> fetchStudents() {
return entityManager.createNamedStoredProcedureQuery("fetchStudents")
.getResultList();
}
}
executeUpdate() vs getResultList():
executeUpdate()— used for procedures that INSERT, UPDATE, or DELETE; returns the affected row count.getResultList()— used for procedures that SELECT; returns aListof mapped entities.
Note: @Autowired is not needed on EntityManager when @PersistenceContext is used — the container injects it correctly. Using both can cause issues on some application servers.
pom.xml 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>
spring-boot-starter-data-jpa brings in Hibernate and EntityManager support.
spring-boot-starter-web provides the REST layer. The MySQL connector is
needed at runtime to talk to the database.
Output Screens
Below are the output screenshots from testing the endpoints:
How It Works (Step-by-Step)
-
Create the procedures in MySQL —
addStudentinserts a row;fetchStudentsreturns all rows. TheDELIMITERchange is required because the body contains semicolons. -
Declare them in the entity —
@NamedStoredProcedureQueriesmaps the Java names to the DB procedures and declares each parameter. -
Inject EntityManager in the service — Spring Boot creates an
EntityManagerand injects it using@PersistenceContext. -
Call the procedure —
entityManager.createNamedStoredProcedureQuery("addStudent")plussetParameter()calls, thenexecuteUpdate()orgetResultList(). -
Transaction required —
@Transactionalensures the persistence context has an active transaction while executing the procedure. -
REST layer — the controller exposes both operations as
POST /addstudentandGET /students.
Customization Ideas
- Use
ParameterMode.OUTto return the inserted row's ID from the procedure. - Add pagination parameters to
fetchStudents(INstart, INsize). - Call a procedure that returns a scalar count using
getSingleResult(). - Add a second procedure to update or delete a student.
- Test the procedure directly in MySQL Workbench using
CALL addStudent(1, 'Deep', 88.5);.
Frequently Asked Questions
How do I call a stored procedure in Spring Boot with JPA?
Declare the procedure using @NamedStoredProcedureQuery on the entity class.
Then inject EntityManager and call
entityManager.createNamedStoredProcedureQuery(name).setParameter(...).executeUpdate()
for INSERT/UPDATE procedures, or .getResultList() for SELECT procedures.
What is @NamedStoredProcedureQuery in JPA?
@NamedStoredProcedureQuery maps a Java name to a database stored procedure.
It is placed on an @Entity class and can be repeated using
@NamedStoredProcedureQueries. It defines the procedure name and its
parameters via @StoredProcedureParameter.
What is the difference between executeUpdate() and getResultList() for procedures?
Use executeUpdate() when the procedure performs INSERT, UPDATE, or DELETE
operations and returns the affected row count. Use getResultList() when the
procedure performs SELECT and returns rows that should be mapped to entity objects.
Why do I need @Transactional when calling stored procedures?
Spring Boot requires @Transactional on the service method to wrap the
procedure call in a transaction. Without it, EntityManager operations throw
TransactionRequiredException because the persistence context needs an active
transaction to execute queries.
How do I create a stored procedure in MySQL?
Use DELIMITER $$ to change the statement terminator, then
CREATE PROCEDURE procedureName(IN param type, ...) BEGIN ... END $$, and
finally reset the delimiter with DELIMITER ;. Using DELIMITER
is required because the procedure body contains semicolons.
Conclusion
In this tutorial, you learned how to create MySQL stored procedures and
call them from Spring Boot using JPA's
@NamedStoredProcedureQuery and EntityManager. You also learned
why @Transactional is required and how to choose between
executeUpdate() and getResultList().
Stored procedures are useful when you need to encapsulate complex SQL logic inside the database — for example, multi-table inserts, reporting queries, or batch operations. Keep business logic in Java where possible, but leverage procedures for performance-critical database operations.
Related topics: Spring Data JPA @Procedure Annotation, OUT Parameters in Stored Procedures, Native Queries vs Stored Procedures, MySQL Cursors and Loops, Transactional Propagation in Spring
No comments:
Post a Comment