- 创建 MySQL 存储过程:先在数据库中定义好一个存储过程。
- Java 准备工作:引入必要的 JDBC 驱动。
- Java 调用存储过程(无参):最简单的调用方式。
- Java 调用存储过程(带输入参数):向存储过程传递数据。
- Java 调用存储过程(带输出参数):从存储过程获取返回数据。
- Java 调用存储过程(同时带输入和输出参数):综合案例。
- 处理存储过程返回的结果集:处理
SELECT查询返回的多行数据。 - 最佳实践与注意事项。
创建 MySQL 存储过程
为了演示,我们在 test 数据库中创建几个不同类型的存储过程。

示例 1:无参,返回一个简单值
-- 切换到数据库
USE test;
-- 创建一个返回 "Hello, World!" 的存储过程
DELIMITER $$
CREATE PROCEDURE say_hello()
BEGIN
SELECT 'Hello, World from MySQL Procedure!' AS greeting;
END$$
DELIMITER ;
示例 2:带输入参数
DELIMITER $$
CREATE PROCEDURE get_user_by_age(IN p_age INT)
BEGIN
SELECT id, name, email
FROM users
WHERE age = p_age;
END$$
DELIMITER ;
(假设 users 表包含 id, name, email, age 字段)
示例 3:带输出参数

DELIMITER $$
CREATE PROCEDURE get_user_count_by_age(IN p_age INT, OUT p_count INT)
BEGIN
SELECT COUNT(*) INTO p_count
FROM users
WHERE age = p_age;
END$$
DELIMITER ;
示例 4:带输入、输出参数和结果集
DELIMITER $$
CREATE PROCEDURE get_user_details(IN p_id INT, OUT p_name VARCHAR(100))
BEGIN
-- 1. 获取用户名并放入输出参数
SELECT name INTO p_name FROM users WHERE id = p_id;
-- 2. 返回该用户的完整信息(结果集)
SELECT id, name, email, age FROM users WHERE id = p_id;
END$$
DELIMITER ;
Java 准备工作
确保你的项目中已经包含了 MySQL JDBC 驱动。
Maven 依赖 (pom.xml)
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version> <!-- 使用合适的版本 -->
</dependency>
JDBC 连接 URL 格式
jdbc:mysql://[host]:[port]/[database]?[properties]

jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC
Java 调用存储过程(无参)
调用 say_hello() 存储过程。
import java.sql.*;
public class CallSimpleProcedure {
private static final String DB_URL = "jdbc:mysql://localhost:3306/test?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 确保资源自动关闭
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS)) {
System.out.println("Database connected successfully!");
// 1. 创建一个 CallableStatement 对象
// {? = call procedure_name()} 表示返回一个值
// {call procedure_name()} 表示不返回值或返回结果集
String sql = "{call say_hello()}";
CallableStatement cstmt = conn.prepareCall(sql);
// 2. 执行存储过程
// 对于返回结果集的存储过程,使用 executeQuery()
// 对于无返回结果集的,使用 execute()
ResultSet rs = cstmt.executeQuery();
// 3. 处理结果集
if (rs.next()) {
String greeting = rs.getString("greeting");
System.out.println("Greeting from DB: " + greeting);
}
// 4. 关闭资源
rs.close();
cstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
Java 调用存储过程(带输入参数)
调用 get_user_by_age(25)。
import java.sql.*;
public class CallProcedureWithInParameter {
// ... DB_URL, USER, PASS 同上 ...
public static void main(String[] args) {
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS)) {
int targetAge = 25;
// 1. 创建 CallableStatement,使用 ? 作为占位符
String sql = "{call get_user_by_age(?)}";
CallableStatement cstmt = conn.prepareCall(sql);
// 2. 设置输入参数 (索引从 1 开始)
cstmt.setInt(1, targetAge);
// 3. 执行并获取结果集
ResultSet rs = cstmt.executeQuery();
// 4. 处理结果集
System.out.println("Users with age " + targetAge + ":");
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
String email = rs.getString("email");
System.out.printf("ID: %d, Name: %s, Email: %s%n", id, name, email);
}
rs.close();
cstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
Java 调用存储过程(带输出参数)
调用 get_user_count_by_age(30, @p_count) 并获取 @p_count 的值。
import java.sql.*;
public class CallProcedureWithOutParameter {
// ... DB_URL, USER, PASS 同上 ...
public static void main(String[] args) {
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS)) {
int targetAge = 30;
// 1. 创建 CallableStatement
// 使用 ? 表示输出参数,需要指定类型
String sql = "{call get_user_count_by_age(?, ?)}";
CallableStatement cstmt = conn.prepareCall(sql);
// 2. 注册输出参数 (索引从 1 开始,第二个 ? 是输出参数)
// Types.INTEGER 是 java.sql.Types 中的常量,表示 SQL 的 INTEGER 类型
cstmt.setInt(1, targetAge); // 设置输入参数
cstmt.registerOutParameter(2, Types.INTEGER); // 注册输出参数
// 3. 执行
// 对于有输出参数的存储过程,通常使用 execute()
cstmt.execute();
// 4. 获取输出参数的值 (在执行之后才能获取)
int userCount = cstmt.getInt(2);
System.out.println("Number of users with age " + targetAge + ": " + userCount);
cstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
Java 调用存储过程(同时带输入和输出参数)
调用 get_user_details(1, @p_name)。
import java.sql.*;
public class CallProcedureInOut {
// ... DB_URL, USER, PASS 同上 ...
public static void main(String[] args) {
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS)) {
int userId = 1;
String sql = "{call get_user_details(?, ?)}";
CallableStatement cstmt = conn.prepareCall(sql);
// 设置输入参数
cstmt.setInt(1, userId);
// 注册输出参数
cstmt.registerOutParameter(2, Types.VARCHAR);
// 执行
cstmt.execute();
// 1. 先获取输出参数的值
String userName = cstmt.getString(2);
System.out.println("User Name (from OUT param): " + userName);
// 2. 再处理返回的结果集
// 注意:如果存储过程有多个结果集,需要使用 getMoreResults() 循环处理
ResultSet rs = cstmt.getResultSet();
if (rs != null) {
System.out.println("User Details (from Result Set):");
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
String email = rs.getString("email");
int age = rs.getInt("age");
System.out.printf("ID: %d, Name: %s, Email: %s, Age: %d%n", id, name, email, age);
}
rs.close();
}
cstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
处理存储过程返回的结果集
当一个存储过程可能返回多个结果集(先执行一个 SELECT,再执行另一个 SELECT)时,需要用循环来处理。
// 假设有一个存储过程 multi_query_proc() 返回两个结果集
CallableStatement cstmt = conn.prepareCall("{call multi_query_proc()}");
boolean hasResult = cstmt.execute();
int resultSetCount = 1;
// hasResult 为 true 表示第一个是结果集
// 后续使用 getMoreResults() 判断是否还有下一个结果集
while (hasResult) {
System.out.println("--- Processing Result Set #" + resultSetCount + " ---");
ResultSet rs = cstmt.getResultSet();
while (rs.next()) {
// 处理当前结果集的每一行
// ...
}
rs.close();
// 移动到下一个结果,并判断它是否是结果集
hasResult = cstmt.getMoreResults();
resultSetCount++;
}
最佳实践与注意事项
- 使用
try-with-resources:这是 Java 7+ 的标准做法,可以自动关闭Connection,Statement,ResultSet等资源,防止内存泄漏。 - 防止 SQL 注入:使用
PreparedStatement或CallableStatement的setXxx()方法来传递参数,而不是用字符串拼接,这可以防止 SQL 注入攻击。 - 明确的异常处理:妥善处理
SQLException,记录错误日志,并向用户友好的提示。 - 代码可读性:将数据库连接信息、SQL 语句等配置化,而不是硬编码在代码中。
- 事务管理:如果存储过程执行的是一个业务单元(比如转账),应该使用 JDBC 事务来保证其原子性。
conn.setAutoCommit(false); // 关闭自动提交 // ... 执行存储过程 ... conn.commit(); // 提交事务 // 或 conn.rollback(); // 出错时回滚
- 选择合适的执行方法:
execute():最通用,可以执行任何 SQL,如果第一个结果是ResultSet,则返回true,否则返回false,适用于有输出参数或多个结果集的情况。executeQuery():仅当确定 SQL 语句会返回一个ResultSet时使用(如SELECT语句),调用存储过程时,如果它只返回一个结果集,也可以使用。executeUpdate():用于执行INSERT,UPDATE,DELETE或不返回结果集的 SQL(如 DDL 语句),返回受影响的行数,调用不返回结果集的存储过程时可以使用。
希望这份详细的指南能帮助你完全掌握在 Java 中调用 MySQL 存储过程!
