杰瑞科技汇

Java如何调用MySQL存储过程?

  1. 创建 MySQL 存储过程:先在数据库中定义好一个存储过程。
  2. Java 准备工作:引入必要的 JDBC 驱动。
  3. Java 调用存储过程(无参):最简单的调用方式。
  4. Java 调用存储过程(带输入参数):向存储过程传递数据。
  5. Java 调用存储过程(带输出参数):从存储过程获取返回数据。
  6. Java 调用存储过程(同时带输入和输出参数):综合案例。
  7. 处理存储过程返回的结果集:处理 SELECT 查询返回的多行数据。
  8. 最佳实践与注意事项

创建 MySQL 存储过程

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

Java如何调用MySQL存储过程?-图1
(图片来源网络,侵删)

示例 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:带输出参数

Java如何调用MySQL存储过程?-图2
(图片来源网络,侵删)
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]

Java如何调用MySQL存储过程?-图3
(图片来源网络,侵删)

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++;
}

最佳实践与注意事项

  1. 使用 try-with-resources:这是 Java 7+ 的标准做法,可以自动关闭 Connection, Statement, ResultSet 等资源,防止内存泄漏。
  2. 防止 SQL 注入:使用 PreparedStatementCallableStatementsetXxx() 方法来传递参数,而不是用字符串拼接,这可以防止 SQL 注入攻击。
  3. 明确的异常处理:妥善处理 SQLException,记录错误日志,并向用户友好的提示。
  4. 代码可读性:将数据库连接信息、SQL 语句等配置化,而不是硬编码在代码中。
  5. 事务管理:如果存储过程执行的是一个业务单元(比如转账),应该使用 JDBC 事务来保证其原子性。
    conn.setAutoCommit(false); // 关闭自动提交
    // ... 执行存储过程 ...
    conn.commit(); // 提交事务
    // 或 conn.rollback(); // 出错时回滚
  6. 选择合适的执行方法
    • execute():最通用,可以执行任何 SQL,如果第一个结果是 ResultSet,则返回 true,否则返回 false,适用于有输出参数或多个结果集的情况。
    • executeQuery():仅当确定 SQL 语句会返回一个 ResultSet 时使用(如 SELECT 语句),调用存储过程时,如果它只返回一个结果集,也可以使用。
    • executeUpdate():用于执行 INSERT, UPDATE, DELETE 或不返回结果集的 SQL(如 DDL 语句),返回受影响的行数,调用不返回结果集的存储过程时可以使用。

希望这份详细的指南能帮助你完全掌握在 Java 中调用 MySQL 存储过程!

分享:
扫描分享到社交APP
上一篇
下一篇