创建一个 MySQL 存储过程
为了演示,我们先在 MySQL 数据库中创建几个不同类型的存储过程。

假设我们有一个名为 test_db 的数据库,里面有一张 employees 表:
CREATE DATABASE IF NOT EXISTS test_db;
USE test_db;
CREATE TABLE employees (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
position VARCHAR(100),
salary DECIMAL(10, 2)
);
INSERT INTO employees (name, position, salary) VALUES
('Alice', 'Developer', 8000.00),
('Bob', 'Manager', 12000.00),
('Charlie', 'Developer', 7500.00);
我们创建几个存储过程:
无参存储过程 (返回结果集) 这个存储过程查询所有员工。
DELIMITER //
CREATE PROCEDURE get_all_employees()
BEGIN
SELECT id, name, position, salary FROM employees;
END //
DELIMITER ;
带输入参数的存储过程 这个存储过程根据职位查询员工。

DELIMITER //
CREATE PROCEDURE get_employees_by_position(IN p_position VARCHAR(100))
BEGIN
SELECT id, name, position, salary FROM employees WHERE position = p_position;
END //
DELIMITER ;
带输入和输出参数的存储过程 这个存储过程计算某个职位的平均工资。
DELIMITER //
CREATE PROCEDURE get_avg_salary_by_position(
IN p_position VARCHAR(100),
OUT p_avg_salary DECIMAL(10, 2)
)
BEGIN
SELECT AVG(salary) INTO p_avg_salary
FROM employees
WHERE position = p_position;
END //
DELIMITER ;
带输入和输出参数,并返回结果集的存储过程 这个存储过程查询某个职位的员工,并通过输出参数返回员工数量。
DELIMITER //
CREATE PROCEDURE get_employees_by_position_with_count(
IN p_position VARCHAR(100),
OUT p_employee_count INT
)
BEGIN
-- 返回结果集
SELECT id, name, position, salary FROM employees WHERE position = p_position;
-- 设置输出参数
SELECT COUNT(*) INTO p_employee_count FROM employees WHERE position = p_position;
END //
DELIMITER ;
Java 调用存储过程
我们将使用 JDBC (Java Database Connectivity) 来调用这些存储过程,确保你已经添加了 MySQL JDBC 驱动的依赖(在 Maven 的 pom.xml 中)。
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version> <!-- 使用合适的版本 -->
</dependency>
下面是完整的 Java 代码示例,展示了如何调用上述四种类型的存储过程。
import java.sql.*;
public class MysqlStoredProcedureExample {
// 数据库连接信息
private static final String DB_URL = "jdbc:mysql://localhost:3306/test_db?useSSL=false&serverTimezone=UTC";
private static final String USER = "root"; // 你的数据库用户名
private static final String PASS = "password"; // 你的数据库密码
public static void main(String[] args) {
// 加载 JDBC 驱动 (对于较新版本的 JDBC 驱动,这步通常是可选的)
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
return;
}
// 使用 try-with-resources 确保连接、语句和结果集被自动关闭
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS)) {
System.out.println("成功连接到数据库!");
// --- 示例 1: 调用无参存储过程 ---
System.out.println("\n--- 示例 1: 调用无参存储过程 get_all_employees ---");
callSimpleProcedure(conn);
// --- 示例 2: 调用带输入参数的存储过程 ---
System.out.println("\n--- 示例 2: 调用带输入参数的存储过程 get_employees_by_position ---");
callProcedureWithInputParam(conn, "Developer");
// --- 示例 3: 调用带输入和输出参数的存储过程 ---
System.out.println("\n--- 示例 3: 调用带输入和输出参数的存储过程 get_avg_salary_by_position ---");
callProcedureWithInOutParam(conn, "Manager");
// --- 示例 4: 调用带输入/输出参数并返回结果集的存储过程 ---
System.out.println("\n--- 示例 4: 调用 get_employees_by_position_with_count ---");
callProcedureWithResultSetAndOutParam(conn, "Developer");
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 示例 1: 调用无参存储过程
*/
private static void callSimpleProcedure(Connection conn) throws SQLException {
// 使用 {call procedure_name()} 语法
String sql = "{call get_all_employees()}";
try (CallableStatement cstmt = conn.prepareCall(sql)) {
// executeQuery() 用于返回结果集的存储过程
ResultSet rs = cstmt.executeQuery();
System.out.println("所有员工信息:");
while (rs.next()) {
System.out.printf("ID: %d, Name: %s, Position: %s, Salary: %.2f%n",
rs.getInt("id"),
rs.getString("name"),
rs.getString("position"),
rs.getBigDecimal("salary"));
}
}
}
/**
* 示例 2: 调用带输入参数的存储过程
*/
private static void callProcedureWithInputParam(Connection conn, String position) throws SQLException {
// ? 占位符代表输入参数
String sql = "{call get_employees_by_position(?)}";
try (CallableStatement cstmt = conn.prepareCall(sql)) {
// 设置输入参数 (索引从1开始)
cstmt.setString(1, position);
ResultSet rs = cstmt.executeQuery();
System.out.println("职位为 '" + position + "' 的员工:");
while (rs.next()) {
System.out.printf("ID: %d, Name: %s, Position: %s, Salary: %.2f%n",
rs.getInt("id"),
rs.getString("name"),
rs.getString("position"),
rs.getBigDecimal("salary"));
}
}
}
/**
* 示例 3: 调用带输入和输出参数的存储过程
*/
private static void callProcedureWithInOutParam(Connection conn, String position) throws SQLException {
// 第一个 ? 是输入参数,第二个 ? 是输出参数
String sql = "{call get_avg_salary_by_position(?, ?)}";
try (CallableStatement cstmt = conn.prepareCall(sql)) {
// 注册输出参数 (必须在使用前注册)
// Types.DECIMAL 对应 DECIMAL 类型
cstmt.registerOutParameter(2, Types.DECIMAL);
// 设置输入参数
cstmt.setString(1, position);
// 执行存储过程
// execute() 用于执行可能返回结果集、更新计数或输出参数的语句
cstmt.execute();
// 获取输出参数
BigDecimal avgSalary = cstmt.getBigDecimal(2);
System.out.println("职位 '" + position + "' 的平均工资是: " + avgSalary);
}
}
/**
* 示例 4: 调用带输入/输出参数并返回结果集的存储过程
*/
private static void callProcedureWithResultSetAndOutParam(Connection conn, String position) throws SQLException {
String sql = "{call get_employees_by_position_with_count(?, ?)}";
try (CallableStatement cstmt = conn.prepareCall(sql)) {
// 注册输出参数
cstmt.registerOutParameter(2, Types.INTEGER);
// 设置输入参数
cstmt.setString(1, position);
// 执行存储过程
// 如果存储过程返回多个结果,需要使用 getResultSet() 和 getUpdateCount() 循环处理
boolean hasResultSet = cstmt.execute();
// 处理第一个结果集 (来自 SELECT 语句)
if (hasResultSet) {
System.out.println("职位为 '" + position + "' 的员工信息:");
ResultSet rs = cstmt.getResultSet();
while (rs.next()) {
System.out.printf("ID: %d, Name: %s, Position: %s, Salary: %.2f%n",
rs.getInt("id"),
rs.getString("name"),
rs.getString("position"),
rs.getBigDecimal("salary"));
}
rs.close();
}
// 检查是否有更多的结果 (来自 OUT 参数)
// 由于我们已经处理了结果集,下一个结果应该是更新计数或 null
// 对于这种情况,更直接的方法是再次调用 getXXX 来获取 OUT 参数
// 或者使用 while (cstmt.getMoreResults()) 循环处理所有结果
// 获取输出参数
int employeeCount = cstmt.getInt(2);
System.out.println("该职位的员工总数为: " + employeeCount);
}
}
}
关键点解析
-
CallableStatement:- 这是用于执行存储过程和函数的
PreparedStatement的子接口,你必须使用它来调用存储过程。
- 这是用于执行存储过程和函数的
-
SQL 语法:
- 调用存储过程的 SQL 语句以
{call ...}开头。 {call get_all_employees()}: 无参。{call get_employees_by_position(?)}: 带一个参数, 是占位符。
- 调用存储过程的 SQL 语句以
-
参数处理:
- 输入参数: 使用
cstmt.setXxx(index, value)来设置,setString(1, "Developer"),索引从 1 开始。 - 输出参数: 必须在使用
execute()之前注册,使用cstmt.registerOutParameter(index, sqlType),registerOutParameter(2, Types.DECIMAL)。Types类是java.sql包中定义的,包含了各种 SQL 类型的常量。
- 输入参数: 使用
-
执行方法:
executeQuery(): 当确定存储过程只返回一个结果集时使用(如示例1和2)。execute(): 当存储过程可能返回结果集、更新计数或一个或多个输出参数时使用(如示例3和4),这是最通用的方法。
-
获取结果:
- 获取结果集:
execute()返回true,表示第一个结果是一个ResultSet,使用cstmt.getResultSet()来获取它。 - 获取输出参数: 在执行完存储过程后,使用
cstmt.getXxx(index)来获取输出参数的值,getBigDecimal(2)。 - 处理多个结果: 如果存储过程返回多个结果(一个
SELECT语句后面跟着一个OUT参数),你需要在一个循环中使用cstmt.getMoreResults()来遍历所有结果,并根据返回值决定是调用getResultSet()还是getUpdateCount()。
- 获取结果集:
调用 MySQL 存储过程的核心步骤如下:
- 获取
Connection对象。 - 使用
Connection.prepareCall("{call procedure_name(...)}")创建CallableStatement对象。 - 如果有输出参数,在执行前用
registerOutParameter()注册。 - 如果有输入参数,用
setXxx()设置。 - 使用
execute()或executeQuery()执行。 - 根据需要,用
getResultSet()获取结果集,或用getXxx()获取输出参数。 - 在
try-with-resources块中管理所有资源,确保它们被正确关闭。
