- MySQL 存储过程准备:我们需要在 MySQL 数据库中创建一个存储过程。
- Java 代码编写:使用 JDBC (Java Database Connectivity) API 来连接数据库并调用这个存储过程。
- 处理不同类型的存储过程:根据存储过程是否带有参数(IN, OUT, INOUT)以及是否返回结果集,Java 调用的方式会有所不同。
第 1 步:MySQL 存储过程示例
为了演示,我们先创建几个不同类型的存储过程。

无参数,返回结果集的存储过程
这个存储过程查询 employees 表并返回所有员工。
-- 创建一个员工表(如果不存在)
CREATE TABLE IF NOT EXISTS employees (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
position VARCHAR(100),
salary DECIMAL(10, 2)
);
-- 插入一些测试数据
INSERT INTO employees (name, position, salary) VALUES
('张三', '软件工程师', 9000.00),
('李四', '项目经理', 15000.00),
('王五', '测试工程师', 7000.00);
-- 创建存储过程:查询所有员工
DELIMITER //
CREATE PROCEDURE get_all_employees()
BEGIN
SELECT id, name, position, salary FROM employees;
END //
DELIMITER ;
带有 IN 和 OUT 参数的存储过程 这个存储过程根据员工 ID 查询员工姓名,并通过 OUT 参数返回薪水。
-- 创建存储过程:根据ID查询员工信息,并通过OUT参数返回薪水
DELIMITER //
CREATE PROCEDURE get_employee_by_id(IN p_id INT, OUT p_salary DECIMAL(10, 2))
BEGIN
SELECT salary INTO p_salary FROM employees WHERE id = p_id;
END //
DELIMITER ;
带有 INOUT 参数的存储过程 这个存储过程接收一个数字,将其乘以 2,然后通过同一个参数返回结果。
-- 创建存储过程:将输入的数字乘以2
DELIMITER //
CREATE PROCEDURE multiply_by_two(INOUT p_value INT)
BEGIN
SET p_value = p_value * 2;
END //
DELIMITER ;
第 2 步:Java 调用存储过程
在 Java 中,我们使用 CallableStatement 对象来调用存储过程,它是 PreparedStatement 的子类,专门用于执行存储过程和数据库函数。

准备工作:添加 JDBC 驱动依赖
确保你的项目中包含了 MySQL JDBC 驱动,如果你使用 Maven,在 pom.xml 中添加:
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version> <!-- 使用你需要的版本 -->
</dependency>
示例 1:调用无参数、返回结果集的存储过程
这是最简单的情况,类似于执行一个普通的 SQL 查询。
import java.sql.*;
public class CallStoredProcedureExample1 {
private static final String DB_URL = "jdbc:mysql://localhost:3306/your_database?useSSL=false&serverTimezone=UTC";
private static final String USER = "your_username";
private static final String PASS = "your_password";
public static void main(String[] args) {
// try-with-resources 确保资源自动关闭
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
CallableStatement cstmt = conn.prepareCall("{call get_all_employees()}")) {
// 执行存储过程
ResultSet rs = cstmt.executeQuery();
// 处理结果集
System.out.println("员工列表:");
System.out.println("ID\t姓名\t职位\t薪水");
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
String position = rs.getString("position");
double salary = rs.getDouble("salary");
System.out.printf("%d\t%s\t%s\t%.2f%n", id, name, position, salary);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
关键点:
conn.prepareCall("{call get_all_employees()}"):这是调用存储过程的核心语法。{call procedure_name()}是标准格式。cstmt.executeQuery():因为存储过程返回一个结果集,所以使用executeQuery()。
示例 2:调用带有 IN 和 OUT 参数的存储过程
这种情况稍微复杂,需要注册 OUT 参数,并在执行后获取其值。
import java.sql.*;
public class CallStoredProcedureExample2 {
private static final String DB_URL = "jdbc:mysql://localhost:3306/your_database?useSSL=false&serverTimezone=UTC";
private static final String USER = "your_username";
private static final String PASS = "your_password";
public static void main(String[] args) {
int employeeId = 1; // 我们想查询ID为1的员工
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
// 语法:{call procedure_name(?, ?)}
// ?1 是 IN 参数, ?2 是 OUT 参数
CallableStatement cstmt = conn.prepareCall("{call get_employee_by_id(?, ?)}")) {
// 设置 IN 参数
cstmt.setInt(1, employeeId);
// 注册 OUT 参数
// 第一个参数是参数的序号(从1开始),第二个参数是 SQL 类型
cstmt.registerOutParameter(2, Types.DECIMAL);
// 执行存储过程
cstmt.execute();
// 获取 OUT 参数的值
double salary = cstmt.getDouble(2);
System.out.println("员工 ID " + employeeId + " 的薪水是: " + salary);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
关键点:
conn.prepareCall("{call get_employee_by_id(?, ?)}"):用 占位符表示参数。cstmt.setInt(1, employeeId):为第一个 (IN 参数)设置值。cstmt.registerOutParameter(2, Types.DECIMAL):这是关键一步,告诉 JDBC 驱动第二个 是一个 OUT 参数,并指定它的 SQL 类型(Types.DECIMAL对应DECIMAL)。cstmt.execute():当存储过程同时包含 IN 和 OUT 参数时,通常使用execute()。cstmt.getDouble(2):执行后,从第二个 中获取返回的值。
示例 3:调用带有 INOUT 参数的存储过程
INOUT 参数结合了 IN 和 OUT 的功能,需要先设置值,再获取值。
import java.sql.*;
public class CallStoredProcedureExample3 {
private static final String DB_URL = "jdbc:mysql://localhost:3306/your_database?useSSL=false&serverTimezone=UTC";
private static final String USER = "your_username";
private static final String PASS = "your_password";
public static void main(String[] args) {
int initialValue = 10;
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
CallableStatement cstmt = conn.prepareCall("{call multiply_by_two(?)}")) {
// 设置 INOUT 参数的初始值
cstmt.setInt(1, initialValue);
// 注册 INOUT 参数
// 对于 INOUT 参数,注册方式和 OUT 参数一样
cstmt.registerOutParameter(1, Types.INTEGER);
// 执行存储过程
cstmt.execute();
// 获取 INOUT 参数返回的值
int resultValue = cstmt.getInt(1);
System.out.println("初始值: " + initialValue);
System.out.println("乘以2后的结果: " + resultValue);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
关键点:
cstmt.setInt(1, initialValue):执行前,为 INOUT 参数设置输入值。cstmt.registerOutParameter(1, Types.INTEGER):注册这个参数为 OUT(或 INOUT)类型。cstmt.getInt(1):执行后,从同一个参数中获取输出值。
总结与最佳实践
| 存储过程类型 | Java 调用关键步骤 |
|---|---|
| 无参数,返回结果集 | prepareCall("{call proc()}") -> executeQuery() -> 处理 ResultSet |
| 带 IN 参数 | prepareCall("{call proc(?, ?)}") -> setXxx(1, value) -> executeQuery() 或 execute() -> 处理 ResultSet |
| 带 OUT 参数 | prepareCall("{call proc(?, ?)}") -> registerOutParameter(2, sqlType) -> execute() -> getXxx(2) |
| 带 INOUT 参数 | prepareCall("{call proc(?)}") -> setXxx(1, value) -> registerOutParameter(1, sqlType) -> execute() -> getXxx(1) |
最佳实践:
- 使用
try-with-resources:确保Connection,Statement,ResultSet等资源在使用后能被正确关闭,防止资源泄漏。 - 处理异常:妥善处理
SQLException,记录错误日志,并向用户友好的提示。 - 使用连接池:在实际应用中,不要每次都创建新的数据库连接,使用如 HikariCP, Druid 等连接池来管理连接,性能会高很多。
- 防止 SQL 注入:虽然存储过程本身可以在一定程度上防止 SQL 注入,但通过
CallableStatement设置参数 (setInt,setString等) 是最安全的做法,永远不要直接拼接 SQL 字符串来调用存储过程。 - 清晰的代码:为存储过程和参数命名时,做到清晰易懂,方便维护。
通过以上示例和总结,你应该能够掌握在 Java 中调用各种类型 MySQL 存储过程的方法。
