第 1 步:在 MySQL 中创建存储过程
我们需要在 MySQL 数据库中创建一个或几个示例存储过程,以便 Java 程序可以调用,我们将创建几种不同类型的存储过程来演示:

- 无参数,返回结果集
- 带 IN 参数
- 带 OUT 参数
- 带 INOUT 参数
假设我们有一个 employees 表:
CREATE DATABASE IF NOT EXISTS mydb;
USE mydb;
CREATE TABLE employees (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
department VARCHAR(100),
salary DECIMAL(10, 2)
);
INSERT INTO employees (name, department, salary) VALUES
('Alice', 'Engineering', 90000.00),
('Bob', 'Sales', 75000.00),
('Charlie', 'Engineering', 110000.00),
('David', 'HR', 65000.00);
示例 1: 无参数,返回结果集
这个存储过程简单查询所有员工。
DELIMITER //
CREATE PROCEDURE GetAllEmployees()
BEGIN
SELECT id, name, department, salary FROM employees;
END //
DELIMITER ;
示例 2: 带 IN 参数
这个存储过程根据部门名称查询员工。
DELIMITER //
CREATE PROCEDURE GetEmployeesByDepartment(IN p_department VARCHAR(100))
BEGIN
SELECT id, name, department, salary FROM employees WHERE department = p_department;
END //
DELIMITER ;
示例 3: 带 OUT 参数
这个存储过程计算指定部门员工的平均薪资,并通过 OUT 参数返回。

DELIMITER //
CREATE PROCEDURE GetAverageSalaryByDepartment(IN p_department VARCHAR(100), OUT p_avg_salary DECIMAL(10, 2))
BEGIN
SELECT AVG(salary) INTO p_avg_salary FROM employees WHERE department = p_department;
END //
DELIMITER ;
示例 4: 带 INOUT 参数
这个存储过程接收一个薪资值,然后为指定部门所有薪资低于此值的员工涨薪 10%,并返回该部门调整后的平均薪资。
DELIMITER //
CREATE PROCEDURE GiveRaiseAndReturnNewAvg(IN p_department VARCHAR(100), INOUT p_threshold_salary DECIMAL(10, 2))
BEGIN
-- 给低于阈值的员工涨薪10%
UPDATE employees
SET salary = salary * 1.10
WHERE department = p_department AND salary < p_threshold_salary;
-- 计算并返回新的平均薪资
SELECT AVG(salary) INTO p_threshold_salary FROM employees WHERE department = p_department;
END //
DELIMITER ;
第 2 步:在 Java 中调用存储过程
我们来看如何在 Java 中调用这些存储过程,我们将使用 JDBC。
准备工作:Maven 依赖
确保你的 pom.xml 文件中包含了 MySQL 驱动依赖。
<dependencies>
<!-- MySQL Connector/J -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version> <!-- 使用你需要的版本 -->
</dependency>
</dependencies>
Java 代码示例
我们将为每个 MySQL 存储过程编写对应的 Java 调用代码。

调用无参数、返回结果集的存储过程 (GetAllEmployees)
这是最简单的情况,我们只需要执行一个 SQL 查询。
import java.sql.*;
public class CallStoredProcedureExample {
// 数据库连接信息
private static final String DB_URL = "jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC";
private static final String USER = "root";
private static final String PASS = "your_password";
public static void main(String[] args) {
// 使用 try-with-resources 确保 Connection, Statement, ResultSet 被自动关闭
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
CallableStatement cstmt = conn.prepareCall("{call GetAllEmployees()}")) {
System.out.println("调用存储过程 GetAllEmployees...");
// 执行存储过程
ResultSet rs = cstmt.executeQuery();
// 处理结果集
System.out.println("ID\tName\t\tDepartment\tSalary");
while (rs.next()) {
System.out.printf("%d\t%s\t\t%s\t\t%.2f%n",
rs.getInt("id"),
rs.getString("name"),
rs.getString("department"),
rs.getBigDecimal("salary"));
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
调用带 IN 参数的存储过程 (GetEmployeesByDepartment)
我们需要使用 setXXX() 方法为 IN 参数赋值。
// ... (DB_URL, USER, PASS 同上)
public static void getEmployeesByDepartment(String department) {
String sql = "{call GetEmployeesByDepartment(?)}";
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
CallableStatement cstmt = conn.prepareCall(sql)) {
// 设置 IN 参数
cstmt.setString(1, department); // 第一个参数 ? 设置为 'Engineering'
System.out.println("\n调用存储过程 GetEmployeesByDepartment for department: " + department);
ResultSet rs = cstmt.executeQuery();
System.out.println("ID\tName\t\tDepartment\tSalary");
while (rs.next()) {
System.out.printf("%d\t%s\t\t%s\t\t%.2f%n",
rs.getInt("id"),
rs.getString("name"),
rs.getString("department"),
rs.getBigDecimal("salary"));
}
} catch (SQLException e) {
e.printStackTrace();
}
}
调用带 OUT 参数的存储过程 (GetAverageSalaryByDepartment)
这是最关键的部分,我们需要:
- 使用
registerOutParameter()注册OUT参数。 - 执行存储过程。
- 使用
getXXX()方法获取OUT参数的值。
// ... (DB_URL, USER, PASS 同上)
public static void getAverageSalary(String department) {
String sql = "{call GetAverageSalaryByDepartment(?, ?)}";
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
CallableStatement cstmt = conn.prepareCall(sql)) {
// 设置 IN 参数
cstmt.setString(1, department);
// 注册 OUT 参数 (第二个参数,类型为 DECIMAL)
// 注意:SQL Types 中的 DECIMAL 对应 java.sql.Types.DECIMAL 或 NUMERIC
cstmt.registerOutParameter(2, java.sql.Types.DECIMAL);
System.out.println("\n调用存储过程 GetAverageSalaryByDepartment for department: " + department);
// 执行存储过程
cstmt.execute();
// 获取 OUT 参数的值
BigDecimal avgSalary = cstmt.getBigDecimal(2);
System.out.println("平均薪资: " + avgSalary);
} catch (SQLException e) {
e.printStackTrace();
}
}
调用带 INOUT 参数的存储过程 (GiveRaiseAndReturnNewAvg)
INOUT 参数结合了 IN 和 OUT 的操作:
- 使用
registerOutParameter()注册它。 - 使用
setXXX()方法为它设置初始值(IN部分)。 - 执行存储过程。
- 使用
getXXX()方法获取它返回的值(OUT部分)。
// ... (DB_URL, USER, PASS 同上)
public static void giveRaiseAndGetNewAvg(String department, double initialThreshold) {
String sql = "{call GiveRaiseAndReturnNewAvg(?, ?)}";
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
CallableStatement cstmt = conn.prepareCall(sql)) {
// 设置 IN 参数
cstmt.setString(1, department);
// 注册 INOUT 参数 (第二个参数)
cstmt.registerOutParameter(2, java.sql.Types.DECIMAL);
// 设置 INOUT 参数的初始值 (IN 部分)
cstmt.setBigDecimal(2, new BigDecimal(initialThreshold));
System.out.println("\n调用存储过程 GiveRaiseAndReturnNewAvg for department: " + department);
System.out.println("初始薪资阈值: " + initialThreshold);
// 执行存储过程
cstmt.execute();
// 获取 INOUT 参数返回的值 (OUT 部分)
BigDecimal newAvgSalary = cstmt.getBigDecimal(2);
System.out.println("涨薪后新的平均薪资: " + newAvgSalary);
} catch (SQLException e) {
e.printStackTrace();
}
}
第 3 步:整合与总结
下面是一个完整的 main 方法,展示了如何调用所有这些函数:
public class Main {
private static final String DB_URL = "jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC";
private static final String USER = "root";
private static final String PASS = "your_password";
public static void main(String[] args) {
// 1. 调用无参数存储过程
CallStoredProcedureExample.getAllEmployees();
// 2. 调用带 IN 参数的存储过程
CallStoredProcedureExample.getEmployeesByDepartment("Engineering");
// 3. 调用带 OUT 参数的存储过程
CallStoredProcedureExample.getAverageSalary("Sales");
// 4. 调用带 INOUT 参数的存储过程
CallStoredProcedureExample.giveRaiseAndGetNewAvg("HR", 70000.00);
}
}
CallableStatement: 这是调用存储过程的核心 JDBC 接口,通过Connection.prepareCall(sql)获取。- SQL 语法: 调用存储过程的 SQL 语句格式为
{call procedure_name(?, ?, ...)}。 是占位符。 - 参数类型:
- IN 参数: 使用
cstmt.setXxx(index, value)设置值。 - OUT 参数: 必须先使用
cstmt.registerOutParameter(index, sqlType)注册,然后执行存储过程后,用cstmt.getXxx(index)获取值。 - INOUT 参数: 必须先
registerOutParameter,setXxx设置输入值,执行后,再用getXxx获取输出值。
- IN 参数: 使用
- 执行方法:
executeQuery(): 用于只返回ResultSet的存储过程(通常是无参数或只有IN参数的SELECT过程)。execute(): 用于更通用的调用,特别是当存储过程有OUT或INOUT参数,或者不返回结果集时,它返回一个布尔值,true表示第一个结果是ResultSet,false表示是更新计数或没有结果。executeUpdate(): 用于执行不返回结果集的存储过程(如INSERT,UPDATE,DELETE或有OUT参数的 DDL)。
- 资源管理: 始终使用
try-with-resources语句来管理Connection,CallableStatement, 和ResultSet,以避免资源泄漏。
通过以上步骤,你应该能够熟练地在 Java 应用程序中调用各种类型的 MySQL 存储过程了。
