Tags: spring-boot jdbc r2dbc java
Calling stored procedures from Spring Boot? You have two paths: JDBC (blocking) or R2DBC (reactive).
it's a sql code that's saved and executed on database server
it's like a resuable function that has business logic
can accept input parameter, produce output parameters
e.g.
There are two distinct stacks for database access in Java:
| JDBC (Blocking) | R2DBC (Reactive) | |
|---|---|---|
| Driver | JDBC Driver | R2DBC Driver |
| Connection | DataSource | ConnectionFactory |
| Client API | JdbcTemplate, SimpleJdbcCall | DatabaseClient |
| ORM | Spring Data JPA | Spring Data R2DBC |
| Annotation | @Procedure |
@Query |
Key insight: These are separate stacks. You can't mix them - JdbcTemplate only works with JDBC drivers, and DatabaseClient only works with R2DBC drivers.
In the JDBC stack, there's an important layer between JdbcTemplate and Spring Data JPA:
JPA (Java Persistence API) is just a specification - interfaces that define what an ORM should do. No actual code.
Hibernate is an implementation of JPA - the actual code that does the work. EclipseLink is another implementation.
Hibernate does NOT work with R2DBC. It's built on JDBC (blocking). That's why @Procedure only exists in the JDBC stack.
These are different concepts:
Repository = A pattern for abstracting data access (find, save, delete). Both stacks have this.
ORM (Object-Relational Mapping) = Maps Java objects ↔ database tables with advanced features:
| Feature | JDBC + Hibernate | R2DBC |
|---|---|---|
| Repository | JpaRepository | ReactiveCrudRepository |
| ORM | Hibernate (full) | None |
| Entity mapping |
@Entity, @OneToMany
|
@Table, @Id (basic)
|
| Lazy loading | Yes | No |
| Relationships | Automatic | Manual |
| Caching | Yes | No |
R2DBC is more "direct" - it talks to the database through drivers without a heavy ORM layer. Less magic, more control, but more manual work for complex scenarios.
Quick ways to identify the stack in code:
| See this? | You're using |
|---|---|
DataSource |
JDBC |
ConnectionFactory |
R2DBC |
@Entity |
JPA/Hibernate (JDBC) |
@Table (without @Entity) |
R2DBC |
JdbcTemplate |
JDBC |
DatabaseClient |
R2DBC |
JpaRepository |
JPA/Hibernate (JDBC) |
ReactiveCrudRepository |
R2DBC |
@Procedure |
JPA (JDBC only) |
Returns Mono/Flux
|
R2DBC |
Connection management is different:
DataSource (blocking connection pool like HikariCP)ConnectionFactory (reactive connection pool)You cannot use DataSource with DatabaseClient - they're from different worlds.
Both are ways to get a database connection, but DataSource is the modern choice.
DriverManager (JDBC 1.0) - the old way:
DataSource (JDBC 2.0+) - the preferred way:
Why DataSource is preferred:
In Spring Boot, just add spring.datasource.* properties and you get a HikariDataSource automatically.
For R2DBC, you use ConnectionFactory instead of DataSource:
In Spring Boot, just add spring.r2dbc.* properties:
Spring Boot auto-configures ConnectionFactory and DatabaseClient for you.
Everything in this stack blocks the thread while waiting for the database.
Full control, but verbose:
Why registerOutParameter? The JDBC driver needs to know which parameters are outputs before execution. After the call completes, it retrieves the value from that registered position.
Declarative, less boilerplate:
@Procedure
The cleanest - but only works with JDBC (blocking):
@Procedure is a JPA annotation. JPA is built on JDBC, so it's inherently blocking. There's no reactive equivalent.
Everything in this stack is non-blocking and returns Mono/Flux.
Two ways to execute queries - same pattern as JDBC:
| Programmatic (Client) | Declarative (Repository) | |
|---|---|---|
| JDBC | JdbcTemplate.query() |
@Query in JpaRepository |
| R2DBC | DatabaseClient.sql() |
@Query in ReactiveCrudRepository |
The reactive equivalent of JdbcTemplate. You write code to build and execute queries manually:
Use this when you need full control - dynamic queries, complex mappings, or operations that don't fit the repository pattern.
@Query in Repository - DeclarativeYou annotate a method and Spring generates the implementation:
Same SQL, but Spring handles the binding and execution. You just call mathRepository.addNumbers(2, 3).
No @Procedure in R2DBC - Spring Data R2DBC doesn't have a @Procedure annotation because R2DBC drivers don't have standardized stored procedure support like JDBC's CallableStatement. You must use @Query with native SQL instead.
| Want to… | Use | Driver |
|---|---|---|
| Call stored procedures with annotations |
@Procedure (JPA) |
JDBC (blocking) |
| Call stored procedures with full control |
JdbcTemplate / SimpleJdbcCall
|
JDBC (blocking) |
| Reactive database access |
DatabaseClient / @Query
|
R2DBC (non-blocking) |
| Reactive stored procedures |
DatabaseClient with native SQL |
R2DBC (limited support) |
If you need @Procedure, you're using JDBC. If you need reactive/non-blocking, you're using R2DBC and will need @Query with native SQL instead.