杰瑞科技汇

Java如何调用MySQL存储过程?

第 1 步:在 MySQL 中创建存储过程

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

Java如何调用MySQL存储过程?-图1
(图片来源网络,侵删)
  1. 无参数,返回结果集
  2. 带 IN 参数
  3. 带 OUT 参数
  4. 带 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 参数返回。

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

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

调用无参数、返回结果集的存储过程 (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)

这是最关键的部分,我们需要:

  1. 使用 registerOutParameter() 注册 OUT 参数。
  2. 执行存储过程。
  3. 使用 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 参数结合了 INOUT 的操作:

  1. 使用 registerOutParameter() 注册它。
  2. 使用 setXXX() 方法为它设置初始值(IN 部分)。
  3. 执行存储过程。
  4. 使用 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);
    }
}
  1. CallableStatement: 这是调用存储过程的核心 JDBC 接口,通过 Connection.prepareCall(sql) 获取。
  2. SQL 语法: 调用存储过程的 SQL 语句格式为 {call procedure_name(?, ?, ...)}。 是占位符。
  3. 参数类型:
    • IN 参数: 使用 cstmt.setXxx(index, value) 设置值。
    • OUT 参数: 必须先使用 cstmt.registerOutParameter(index, sqlType) 注册,然后执行存储过程后,用 cstmt.getXxx(index) 获取值。
    • INOUT 参数: 必须先 registerOutParametersetXxx 设置输入值,执行后,再用 getXxx 获取输出值。
  4. 执行方法:
    • executeQuery(): 用于只返回 ResultSet 的存储过程(通常是无参数或只有 IN 参数的 SELECT 过程)。
    • execute(): 用于更通用的调用,特别是当存储过程有 OUTINOUT 参数,或者不返回结果集时,它返回一个布尔值,true 表示第一个结果是 ResultSetfalse 表示是更新计数或没有结果。
    • executeUpdate(): 用于执行不返回结果集的存储过程(如 INSERT, UPDATE, DELETE 或有 OUT 参数的 DDL)。
  5. 资源管理: 始终使用 try-with-resources 语句来管理 Connection, CallableStatement, 和 ResultSet,以避免资源泄漏。

通过以上步骤,你应该能够熟练地在 Java 应用程序中调用各种类型的 MySQL 存储过程了。

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