杰瑞科技汇

Java如何调用MySQL存储过程?

创建一个 MySQL 存储过程

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

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

假设我们有一个名为 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 ;

带输入参数的存储过程 这个存储过程根据职位查询员工。

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

关键点解析

  1. CallableStatement:

    • 这是用于执行存储过程和函数的 PreparedStatement 的子接口,你必须使用它来调用存储过程。
  2. SQL 语法:

    • 调用存储过程的 SQL 语句以 {call ...} 开头。
    • {call get_all_employees()}: 无参。
    • {call get_employees_by_position(?)}: 带一个参数, 是占位符。
  3. 参数处理:

    • 输入参数: 使用 cstmt.setXxx(index, value) 来设置,setString(1, "Developer"),索引从 1 开始。
    • 输出参数: 必须在使用 execute() 之前注册,使用 cstmt.registerOutParameter(index, sqlType)registerOutParameter(2, Types.DECIMAL)Types 类是 java.sql 包中定义的,包含了各种 SQL 类型的常量。
  4. 执行方法:

    • executeQuery(): 当确定存储过程只返回一个结果集时使用(如示例1和2)。
    • execute(): 当存储过程可能返回结果集、更新计数或一个或多个输出参数时使用(如示例3和4),这是最通用的方法。
  5. 获取结果:

    • 获取结果集: execute() 返回 true,表示第一个结果是一个 ResultSet,使用 cstmt.getResultSet() 来获取它。
    • 获取输出参数: 在执行完存储过程后,使用 cstmt.getXxx(index) 来获取输出参数的值,getBigDecimal(2)
    • 处理多个结果: 如果存储过程返回多个结果(一个 SELECT 语句后面跟着一个 OUT 参数),你需要在一个循环中使用 cstmt.getMoreResults() 来遍历所有结果,并根据返回值决定是调用 getResultSet() 还是 getUpdateCount()

调用 MySQL 存储过程的核心步骤如下:

  1. 获取 Connection 对象。
  2. 使用 Connection.prepareCall("{call procedure_name(...)}") 创建 CallableStatement 对象。
  3. 如果有输出参数,在执行前用 registerOutParameter() 注册。
  4. 如果有输入参数,用 setXxx() 设置。
  5. 使用 execute()executeQuery() 执行。
  6. 根据需要,用 getResultSet() 获取结果集,或用 getXxx() 获取输出参数。
  7. try-with-resources 块中管理所有资源,确保它们被正确关闭。
分享:
扫描分享到社交APP
上一篇
下一篇