1. 概述

存储过程(Stored Procedure)是一组预定义的 SQL 语句,保存在数据库中。在 Java 应用中,有多种方式可以调用这些存储过程。本文将重点介绍如何通过 Spring Data JPA 仓库(Repository) 来调用存储过程。

这种方式适合已有 JPA 架构、又需要对接遗留数据库逻辑或性能敏感场景的项目。✅ 掌握它,能让你在处理复杂查询或批量操作时更游刃有余。


2. 项目配置

我们使用 Spring Boot Starter Data JPA 作为数据访问层,搭配 MySQL 作为后端数据库。

需要在 pom.xml 中引入以下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

⚠️ 注意:spring-boot-starter-data-jdbc 并非必须,但若涉及更底层的调用或测试,建议保留。

接着,在 application.properties 中配置数据库连接:

spring.datasource.url=jdbc:mysql://localhost:3306/baeldung
spring.datasource.username=root
spring.datasource.password=secret123
spring.jpa.hibernate.ddl-auto=none

这里假设数据库名为 baeldung,账号密码已根据实际环境 mock。


3. 实体类定义

在 Spring Data JPA 中,实体(Entity)用于映射数据库表结构。我们创建一个 Car 实体来对应 car 表:

@Entity
public class Car {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column
    private long id;

    @Column
    private String model;

    @Column
    private Integer year;

    // standard getters and setters
}

这个类很标准,不做过多解释。老手都懂,略过。✅


4. 存储过程创建

数据库中的存储过程可以带参数,支持输入(IN)、输出(OUT)甚至双向(INOUT)。我们来看两个典型例子。

4.1 带输入参数的查询型存储过程

返回某一年份之后的所有车辆记录:

CREATE PROCEDURE FIND_CARS_AFTER_YEAR(IN year_in INT)
BEGIN 
    SELECT * FROM car WHERE year >= year_in ORDER BY year;
END

4.2 带输出参数的统计型存储过程

根据车型统计数量,并通过输出参数返回结果:

CREATE PROCEDURE GET_TOTAL_CARS_BY_MODEL(IN model_in VARCHAR(50), OUT count_out INT)
BEGIN
    SELECT COUNT(*) INTO count_out FROM car WHERE model = model_in;
END

📌 踩坑提醒:MySQL 中 INTO 关键字不能少,否则会报错 ❌。


5. 在 Repository 中引用存储过程

Spring Data JPA 提供了多种方式在 Repository 接口里调用存储过程。下面介绍三种主流做法,各有适用场景。

@Repository
public interface CarRepository extends JpaRepository<Car, Long> {
    // 后续方法将在这里添加
}

注意主键类型改为 Long 以匹配 Car.idlong 类型。

5.1 直接映射存储过程名称

使用 @Procedure 注解,将方法直接绑定到数据库中的存储过程名。

有四种写法,效果等价:

方式一:方法名即存储过程名(不推荐)

@Procedure
int GET_TOTAL_CARS_BY_MODEL(String model);

⚠️ 违背 Java 命名规范,可读性差,建议避免。

方式二:使用注解 value(常用)

@Procedure("GET_TOTAL_CARS_BY_MODEL")
int getTotalCarsByModel(String model);

简洁明了,✅ 推荐日常使用。

方式三:显式使用 procedureName 属性

@Procedure(procedureName = "GET_TOTAL_CARS_BY_MODEL")
int getTotalCarsByModelProcedureName(String model);

语义清晰,但略啰嗦,适合强调意图的场景。

方式四:使用 value 属性(同方式二)

@Procedure(value = "GET_TOTAL_CARS_BY_MODEL")
int getTotalCarsByModelValue(String model);

value 是默认属性,可省略,和方式二一致。

📌 总结:推荐 方式二,简单粗暴又符合习惯。


5.2 引用实体中定义的存储过程

通过 @NamedStoredProcedureQuery 在实体类中声明存储过程元信息,实现解耦。

修改 Car 实体:

@Entity
@NamedStoredProcedureQuery(
    name = "Car.getTotalCarsByModelEntity", 
    procedureName = "GET_TOTAL_CARS_BY_MODEL", 
    parameters = {
        @StoredProcedureParameter(mode = ParameterMode.IN, name = "model_in", type = String.class),
        @StoredProcedureParameter(mode = ParameterMode.OUT, name = "count_out", type = Integer.class)
    }
)
public class Car {
    // 其他字段和方法
}

然后在 Repository 中引用:

@Procedure(name = "Car.getTotalCarsByModelEntity")
int getTotalCarsByModelEntity(@Param("model_in") String model);

✅ 优势:

  • 存储过程定义集中管理
  • 支持 IDE 跳转和编译期检查
  • 多 Repository 复用方便

⚠️ 缺点:

  • 实体类变得臃肿
  • 耦合度上升,不适合通用实体

适合中大型项目中对存储过程调用较频繁的场景。


5.3 使用 @Query 注解直接调用

最灵活的方式,直接写原生 SQL 调用存储过程。

@Query(value = "CALL FIND_CARS_AFTER_YEAR(:year_in);", nativeQuery = true)
List<Car> findCarsAfterYear(@Param("year_in") Integer year_in);

✅ 优势:

  • 写法直观,无需额外注解
  • 支持复杂调用逻辑
  • 可混合其他 SQL 操作

📌 注意事项:

  • 必须设置 nativeQuery = true
  • 参数用 :paramName 绑定,配合 @Param 使用
  • 返回类型需与实体或结果集匹配

适用于一次性、复杂或动态拼接的场景,灵活度最高。


6. 总结

本文系统介绍了在 Spring Data JPA 中调用存储过程的三种方式:

方式 使用场景 推荐指数
@Procedure 直接映射 简单调用,快速上手 ⭐⭐⭐⭐
@NamedStoredProcedureQuery 多处复用,需集中管理 ⭐⭐⭐⭐☆
@Query(nativeQuery = true) 复杂逻辑,灵活性要求高 ⭐⭐⭐⭐⭐

📌 选择建议:

  • 日常开发优先考虑 @Procedure + 方法名映射
  • 中大型项目推荐结合 @NamedStoredProcedureQuery 统一管理
  • 特殊场景直接上 @Query,简单粗暴有效

所有示例代码均可在 GitHub 获取:https://github.com/example/spring-data-jpa-stored-procedure-demo(已 mock 地址)

掌握这几种方式,面对 legacy DB 或性能瓶颈时,你就能从容应对了。💪


原始标题:Calling Stored Procedures from Spring Data JPA Repositories | Baeldung