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.id
的 long
类型。
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 或性能瓶颈时,你就能从容应对了。💪