Native SQL and Stored Procedures in JPA/Hibernate
When Abstraction Fails
JPA (JPQL/Criteria API) covers 90% of use cases. However, for the remaining 10%—complex reports, bulk operations, or database-specific features—you need to use Native SQL or call Stored Procedures. This guide explains how to properly “break the abstraction” in a Java enterprise environment.
Core Concepts
1. Native Queries
Standard SQL queries executed via the EntityManager. Unlike JPQL, these are not parsed into an Abstract Syntax Tree by Hibernate; they are passed directly to the database driver.
- Pros: Access to DB-specific features (Window functions, CTEs).
- Cons: Loss of database portability.
2. Stored Procedures
Pre-compiled SQL code stored in the database. JPA provides @NamedStoredProcedureQuery to call these in a semi-typesafe way, handling both input and output parameters.
Practice Exercise: Executing a Stored Procedure
We will implement a call to a stored procedure that calculates a user’s loyalty discount.
Step 1: The Database Procedure (PostgreSQL)
CREATE OR REPLACE PROCEDURE calculate_discount(
IN user_id INT,
OUT discount_percent NUMERIC
)
LANGUAGE plpgsql
AS $$
BEGIN
SELECT 10.0 INTO discount_percent; -- Simplified logic
END;
$$;
Step 2: Mapping in Java
Define the procedure call within your @Entity.
@Entity
@NamedStoredProcedureQuery(
name = "User.calculateDiscount",
procedureName = "calculate_discount",
parameters = {
@StoredProcedureParameter(mode = ParameterMode.IN, name = "user_id", type = Integer.class),
@StoredProcedureParameter(mode = ParameterMode.OUT, name = "discount_percent", type = Double.class)
}
)
public class User {
// ... fields ...
}
Step 3: Execution via EntityManager
StoredProcedureQuery query = entityManager.createNamedStoredProcedureQuery("User.calculateDiscount");
query.setParameter("user_id", 123);
query.execute();
Double result = (Double) query.getOutputParameterValue("discount_percent");
Why Use Native Queries?
- Bulk Operations: Standard JPA
deletecan be slow as it might execute one query per row to manage the lifecycle. NativeDELETE FROM ...is near-instant. - Advanced SQL: Window functions (
RANK(),LEAD()), JSONB operators (in PostgreSQL), and Spatial functions often require raw SQL. - DTO Mapping: You can map native query results directly to non-entity DTOs using
@SqlResultSetMappingor Spring Data Projections.
Security Warning: SQL Injection
Never concatenate strings in native queries. Always use positional or named parameters to prevent SQL injection attacks.
// SUCCESS: Parameterized
Query q = em.createNativeQuery("SELECT * FROM users WHERE email = :email");
q.setParameter("email", emailInput);
// FAILURE: Vulnerable to Injection
Query q = em.createNativeQuery("SELECT * FROM users WHERE email = '" + emailInput + "'");
Summary
Knowing when to “break the abstraction” is a hallmark of a senior developer. Use JPA for most business logic and raw SQL for performance-critical or complex database operations, ensuring your Java application remains maintainable, secure, and fast.