杰瑞科技汇

Java如何调用MySQL存储过程?

  1. MySQL 存储过程准备:我们需要在 MySQL 数据库中创建一个存储过程。
  2. Java 代码编写:使用 JDBC (Java Database Connectivity) API 来连接数据库并调用这个存储过程。
  3. 处理不同类型的存储过程:根据存储过程是否带有参数(IN, OUT, INOUT)以及是否返回结果集,Java 调用的方式会有所不同。

第 1 步:MySQL 存储过程示例

为了演示,我们先创建几个不同类型的存储过程。

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

无参数,返回结果集的存储过程 这个存储过程查询 employees 表并返回所有员工。

-- 创建一个员工表(如果不存在)
CREATE TABLE IF NOT EXISTS employees (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100),
    position VARCHAR(100),
    salary DECIMAL(10, 2)
);
-- 插入一些测试数据
INSERT INTO employees (name, position, salary) VALUES
('张三', '软件工程师', 9000.00),
('李四', '项目经理', 15000.00),
('王五', '测试工程师', 7000.00);
-- 创建存储过程:查询所有员工
DELIMITER //
CREATE PROCEDURE get_all_employees()
BEGIN
    SELECT id, name, position, salary FROM employees;
END //
DELIMITER ;

带有 IN 和 OUT 参数的存储过程 这个存储过程根据员工 ID 查询员工姓名,并通过 OUT 参数返回薪水。

-- 创建存储过程:根据ID查询员工信息,并通过OUT参数返回薪水
DELIMITER //
CREATE PROCEDURE get_employee_by_id(IN p_id INT, OUT p_salary DECIMAL(10, 2))
BEGIN
    SELECT salary INTO p_salary FROM employees WHERE id = p_id;
END //
DELIMITER ;

带有 INOUT 参数的存储过程 这个存储过程接收一个数字,将其乘以 2,然后通过同一个参数返回结果。

-- 创建存储过程:将输入的数字乘以2
DELIMITER //
CREATE PROCEDURE multiply_by_two(INOUT p_value INT)
BEGIN
    SET p_value = p_value * 2;
END //
DELIMITER ;

第 2 步:Java 调用存储过程

在 Java 中,我们使用 CallableStatement 对象来调用存储过程,它是 PreparedStatement 的子类,专门用于执行存储过程和数据库函数。

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

准备工作:添加 JDBC 驱动依赖

确保你的项目中包含了 MySQL JDBC 驱动,如果你使用 Maven,在 pom.xml 中添加:

<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.0.33</version> <!-- 使用你需要的版本 -->
</dependency>

示例 1:调用无参数、返回结果集的存储过程

这是最简单的情况,类似于执行一个普通的 SQL 查询。

import java.sql.*;
public class CallStoredProcedureExample1 {
    private static final String DB_URL = "jdbc:mysql://localhost:3306/your_database?useSSL=false&serverTimezone=UTC";
    private static final String USER = "your_username";
    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);
             CallableStatement cstmt = conn.prepareCall("{call get_all_employees()}")) {
            // 执行存储过程
            ResultSet rs = cstmt.executeQuery();
            // 处理结果集
            System.out.println("员工列表:");
            System.out.println("ID\t姓名\t职位\t薪水");
            while (rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                String position = rs.getString("position");
                double salary = rs.getDouble("salary");
                System.out.printf("%d\t%s\t%s\t%.2f%n", id, name, position, salary);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

关键点

  • conn.prepareCall("{call get_all_employees()}"):这是调用存储过程的核心语法。{call procedure_name()} 是标准格式。
  • cstmt.executeQuery():因为存储过程返回一个结果集,所以使用 executeQuery()

示例 2:调用带有 IN 和 OUT 参数的存储过程

这种情况稍微复杂,需要注册 OUT 参数,并在执行后获取其值。

import java.sql.*;
public class CallStoredProcedureExample2 {
    private static final String DB_URL = "jdbc:mysql://localhost:3306/your_database?useSSL=false&serverTimezone=UTC";
    private static final String USER = "your_username";
    private static final String PASS = "your_password";
    public static void main(String[] args) {
        int employeeId = 1; // 我们想查询ID为1的员工
        try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
             // 语法:{call procedure_name(?, ?)}
             // ?1 是 IN 参数, ?2 是 OUT 参数
             CallableStatement cstmt = conn.prepareCall("{call get_employee_by_id(?, ?)}")) {
            // 设置 IN 参数
            cstmt.setInt(1, employeeId);
            // 注册 OUT 参数
            // 第一个参数是参数的序号(从1开始),第二个参数是 SQL 类型
            cstmt.registerOutParameter(2, Types.DECIMAL);
            // 执行存储过程
            cstmt.execute();
            // 获取 OUT 参数的值
            double salary = cstmt.getDouble(2);
            System.out.println("员工 ID " + employeeId + " 的薪水是: " + salary);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

关键点

  • conn.prepareCall("{call get_employee_by_id(?, ?)}"):用 占位符表示参数。
  • cstmt.setInt(1, employeeId):为第一个 (IN 参数)设置值。
  • cstmt.registerOutParameter(2, Types.DECIMAL)这是关键一步,告诉 JDBC 驱动第二个 是一个 OUT 参数,并指定它的 SQL 类型(Types.DECIMAL 对应 DECIMAL)。
  • cstmt.execute():当存储过程同时包含 IN 和 OUT 参数时,通常使用 execute()
  • cstmt.getDouble(2):执行后,从第二个 中获取返回的值。

示例 3:调用带有 INOUT 参数的存储过程

INOUT 参数结合了 IN 和 OUT 的功能,需要先设置值,再获取值。

import java.sql.*;
public class CallStoredProcedureExample3 {
    private static final String DB_URL = "jdbc:mysql://localhost:3306/your_database?useSSL=false&serverTimezone=UTC";
    private static final String USER = "your_username";
    private static final String PASS = "your_password";
    public static void main(String[] args) {
        int initialValue = 10;
        try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
             CallableStatement cstmt = conn.prepareCall("{call multiply_by_two(?)}")) {
            // 设置 INOUT 参数的初始值
            cstmt.setInt(1, initialValue);
            // 注册 INOUT 参数
            // 对于 INOUT 参数,注册方式和 OUT 参数一样
            cstmt.registerOutParameter(1, Types.INTEGER);
            // 执行存储过程
            cstmt.execute();
            // 获取 INOUT 参数返回的值
            int resultValue = cstmt.getInt(1);
            System.out.println("初始值: " + initialValue);
            System.out.println("乘以2后的结果: " + resultValue);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

关键点

  • cstmt.setInt(1, initialValue):执行前,为 INOUT 参数设置输入值。
  • cstmt.registerOutParameter(1, Types.INTEGER):注册这个参数为 OUT(或 INOUT)类型。
  • cstmt.getInt(1):执行后,从同一个参数中获取输出值。

总结与最佳实践

存储过程类型 Java 调用关键步骤
无参数,返回结果集 prepareCall("{call proc()}") -> executeQuery() -> 处理 ResultSet
带 IN 参数 prepareCall("{call proc(?, ?)}") -> setXxx(1, value) -> executeQuery()execute() -> 处理 ResultSet
带 OUT 参数 prepareCall("{call proc(?, ?)}") -> registerOutParameter(2, sqlType) -> execute() -> getXxx(2)
带 INOUT 参数 prepareCall("{call proc(?)}") -> setXxx(1, value) -> registerOutParameter(1, sqlType) -> execute() -> getXxx(1)

最佳实践

  1. 使用 try-with-resources:确保 Connection, Statement, ResultSet 等资源在使用后能被正确关闭,防止资源泄漏。
  2. 处理异常:妥善处理 SQLException,记录错误日志,并向用户友好的提示。
  3. 使用连接池:在实际应用中,不要每次都创建新的数据库连接,使用如 HikariCP, Druid 等连接池来管理连接,性能会高很多。
  4. 防止 SQL 注入:虽然存储过程本身可以在一定程度上防止 SQL 注入,但通过 CallableStatement 设置参数 (setInt, setString 等) 是最安全的做法,永远不要直接拼接 SQL 字符串来调用存储过程。
  5. 清晰的代码:为存储过程和参数命名时,做到清晰易懂,方便维护。

通过以上示例和总结,你应该能够掌握在 Java 中调用各种类型 MySQL 存储过程的方法。

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